C# 带有CSharpCodeProvider生成的DLL的TargetInvocationException异常
我正在尝试创建一个脚本系统,它使用C的CSharpCodeProvider在运行时构建C代码。这是我正在开发的一个简单的游戏引擎,用XNA4.0编写。 目标是让用户能够通过C#修改游戏元素,而无需访问游戏引擎的具体细节(渲染代码、物理代码、网络等)。脚本在运行时由引擎编译成DLL。目前,我已经建立了引擎和编译脚本DLL之间的通信。(我创建了一个Player.cs脚本,编译后它可以从脚本DLL调用引擎的“engine.Print”(“Foobar”);方法)引擎也可以使用脚本的方法(编译后引擎搜索脚本中定义的所有新类,并在编译后调用它们的“OnCompile()”方法 问题从脚本间通信开始:我有两个脚本,库存和播放器: Inventory.cs:C# 带有CSharpCodeProvider生成的DLL的TargetInvocationException异常,c#,scripting,C#,Scripting,我正在尝试创建一个脚本系统,它使用C的CSharpCodeProvider在运行时构建C代码。这是我正在开发的一个简单的游戏引擎,用XNA4.0编写。 目标是让用户能够通过C#修改游戏元素,而无需访问游戏引擎的具体细节(渲染代码、物理代码、网络等)。脚本在运行时由引擎编译成DLL。目前,我已经建立了引擎和编译脚本DLL之间的通信。(我创建了一个Player.cs脚本,编译后它可以从脚本DLL调用引擎的“engine.Print”(“Foobar”);方法)引擎也可以使用脚本的方法(编译后引擎搜索
public class Inventory
{
int foobar;
public Inventory()
{
foobar = 42;
}
public static void OnCompile()
{
// This method exists in the Engine DLL, linked to this script
Engine.Print("OnCompile Inventory");
}
}
Player.cs:
using Scripts.Inventory;
public class Player
{
Inventory inventory;
public Player()
{
//inventory = new Inventory();
Engine.Print("Player created");
}
public static void OnCompile()
{
Engine.Print("OnCompile Player");
Player test = new Player();
}
}
此代码起作用,调试输出打印:
OnCompile目录奥运选手
玩家创建 但是,一旦我取消对播放器构造函数中的inventory=newinventory()的注释 调试输出如下所示: OnCompile目录
奥运选手 未处理的异常:System.Reflection.TargetingException:调用的目标已引发异常。-->System.IO.FileNotFoundException:无法加载文件或程序集'Inventory.cs,Version=0.0.0,Culture=neutral,PublicKeyToken=null'或其依赖项之一。系统找不到指定的文件。 在Scripts.Player.Player..ctor()处 在Scripts.Player.Player.OnCompile()中 我已确保我的Player.cs.dll引用了Inventory.cs.dll。我的编译代码如下:
public static bool Compile(string fileName, bool forceRecompile = false)
{
// Check to see if this assembly already exists.
// If it does, then just return a reference to it, unless
// it is told to forceRecompile, in which case
// it will delete the old, and continue compiling
if (File.Exists("./" + fileName + ".dll"))
{
if (forceRecompile)
{
File.Delete(fileName + ".dll");
}
else
{
return true;
}
}
// Generate a name space name. this means removing the initial ./
// of the path, and replacing all subsequent /'s with .'s
// Also removing the .cs at the end
// i.e: ./Scripts/Player.cs becomes
// Scripts.Player
string namespaceName = "";
if (fileName.LastIndexOf('.') != -1)
{
fileName = fileName.Remove(fileName.LastIndexOf('.'));
}
namespaceName = fileName.Replace('/', '.');
namespaceName = namespaceName.Substring(2);
// Add references, starting with ScriptBase.dll.
// ScriptBase.dll is a helper library that provides
// access to debug functions such as Console.Write
List<string> references = new List<string>()
{
"./ScriptBase.dll",
"System.dll" // TODO: remove later
};
// Open the script file wit ha StreamReader
StreamReader fileStream;
string scriptSource = "";
fileStream = File.OpenText("./" + fileName + ".cs");
// Preprocess the script. This is important, as it resolves
// using statements, so that if a script references another
// script, it will have the dependency registered before
// compiling.
do
{
string line = fileStream.ReadLine();
string[] words = line.Split(' ');
// Found a using statement:
if (words[0] == "using")
{
// Get the namepsace name:
string library = words[1];
library = library.Remove(library.Length - 1); // get rid of semicolon
// Convert back to a path
library = library.Replace('.', '/');
// See if the assembly exists, or we are forcing the recompilation
if (!File.Exists("./" + library + ".cs.dll") || forceRecompile)
{
// We need to compile it now.
// See if the script file exists...
if (File.Exists("./" + library + ".cs"))
{
// if it does, compile that, if it doesn't then we bail
if (!Compile("./" + library + ".cs", forceRecompile))
{
return false;
}
}
else
{
return false;
}
}
// Now that it's compiled, and we need it link it with our reference list...
references.Add("./" + library + ".cs.dll");
}
// Piece it back together as one string, line by line.
scriptSource = scriptSource + line + "\n";
} while (!fileStream.EndOfStream);
fileStream.Close();
// Automagically add our namepsace to the script, so the scriptor doesn't have to, also automatically
// include ScriptBase
// This is where Engine class is found for Print() debug method
string source = "using ScriptBase; namespace " + namespaceName + "{" + scriptSource + "}";
// Set up the compiler:
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{ "CompilerVersion", "v3.5" }
};
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
// Create compilation params... Here we link our references, and append ".cs.dll" to our file name
// So now for example, ./Scripts/Player.cs compiles to ./Script/Player.cs.dll
CompilerParameters compilerParams = new CompilerParameters(references.ToArray(), fileName + ".cs.dll")
{
GenerateInMemory = true,
GenerateExecutable = false, // compile as DLL
};
// Compile and check errors
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
if (results.Errors.Count != 0)
{
foreach (CompilerError error in results.Errors)
{
// Write out any errors found:
Console.WriteLine("Syntax Error in " + error.FileName + " (" + error.Line + "): " + error.ErrorText);
}
return false;
}
// Return our Script struct, which keeps all the information together,
// and registers it so that Script.GetCompiledScript("./Scripts/Player.cs.dll");
// returns the compiled script, or null if it's never been compiled
Assembly.LoadFrom(fileName + ".cs.dll");
foreach (Type type in results.CompiledAssembly.GetTypes())
{
new ScriptClass(fileName + ".cs.dll", type);
}
return true;
}
}
公共静态bool编译(字符串文件名,bool forceRecompile=false)
{
//检查此程序集是否已存在。
//如果是,则只返回对它的引用,除非
//它被告知强制重新编译,在这种情况下
//它将删除旧的,并继续编译
如果(File.Exists(“./”+文件名+“.dll”))
{
如果(强制重新编译)
{
删除(文件名+“.dll”);
}
其他的
{
返回true;
}
}
//生成名称空间名称。这意味着删除首字母/
//,并将所有后续的/'s替换为''s'
//同时删除结尾处的.cs
//即:./Scripts/Player.cs成为
//脚本,播放器
字符串namespaceName=“”;
如果(fileName.LastIndexOf('.')!=-1)
{
fileName=fileName.Remove(fileName.LastIndexOf('.');
}
namespaceName=fileName.Replace(“/”,“.”);
namespaceName=namespaceName.Substring(2);
//添加引用,从ScriptBase.dll开始。
//ScriptBase.dll是一个帮助程序库,提供
//对调试函数(如Console.Write)的访问
列表引用=新列表()
{
“/ScriptBase.dll”,
“System.dll”//TODO:稍后删除
};
//使用ha StreamReader打开脚本文件
StreamReader文件流;
字符串scriptSource=“”;
fileStream=File.OpenText(“./”+fileName+“.cs”);
//预处理脚本。这很重要,因为它解决了
//使用语句,以便如果脚本引用另一个
//脚本,它将在之前注册依赖项
//编译。
做
{
string line=fileStream.ReadLine();
string[]words=line.Split(“”);
//找到一个using语句:
如果(字[0]=“使用”)
{
//获取名称共享名称:
字符串库=单词[1];
library=library.Remove(library.Length-1);//去掉分号
//转换回路径
library=library.Replace('.','/');
//看看程序集是否存在,或者我们正在强制重新编译
如果(!File.Exists(“./”+library+“.cs.dll”)| |强制重新编译)
{
//我们现在需要编译它。
//查看脚本文件是否存在。。。
如果(文件.Exists(“./”+库+“.cs”))
{
//如果有,编译它,如果没有,我们就退出
如果(!Compile(“./”+库+“.cs”,强制重新编译))
{
返回false;
}
}
其他的
{
返回false;
}
}
//现在它已经编译好了,我们需要它和我们的参考列表链接起来。。。
添加(“./”+库+“.cs.dll”);
}
//一行一行地把它拼成一根绳子。
scriptSource=scriptSource+line+“\n”;
}而(!fileStream.EndOfStream);
fileStream.Close();
//自动地将我们的名字添加到脚本中,这样脚本编写者就不必自动添加名字
//包括脚本库
//这是为Print()调试方法找到引擎类的地方
string source=“使用ScriptBase;命名空间”+namespaceName+“{”+scriptSource+“}”;
//设置编译器:
Dictionary providerOptions=新字典
{
{“compilervision”,“v3.5”}
};
CSharpCodeProvider provider=新的CSharpCodeProvider(providerOptions);
//创建编译参数…这里我们链接引用,并将“.cs.dll”附加到文件名
//例如,现在,./Script