我正在嘗試創建一個使用C#的CSharpCodeProvider在運行時生成C#代碼的腳本系統。這是一個我正在編寫的簡單的遊戲引擎,用XNA 4.0編寫。 目標是讓用戶能夠通過C#修改遊戲元素,而無需訪問遊戲引擎的任何細節(渲染代碼,物理代碼,網絡等)。腳本在運行時由引擎編譯爲DLL。目前,我在引擎和已編譯的腳本DLL之間建立了通信。 (我創建了一個Player.cs腳本,並且在編譯之後,它能夠從腳本DLL中調用我的引擎的「Engine.Print(」Foobar「);方法)引擎還能夠使用腳本的方法(引擎遍歷在編譯後的腳本所定義的所有新類,並調用編譯後的「OnCompile()」方法TargetInvocationException與CSharpCodeProvider生成的DLL
問題與腳本間通信的開始:我有2個腳本,庫存和播放器:
Inventory.cs :
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庫存
OnCompile播放
Player創建
然而,一旦我去掉庫存=新庫存();在播放器的構造 調試輸出如下:
OnCompile庫存
OnCompile球員
未處理的異常:System.Reflection.TargetInvocationException:異常已通過調用的目標引發異常。 ---> System.IO.FileNotFoundException:無法加載文件或程序集'Inventory.cs,Version = 0.0.0.0,Culture = neutral,PublicKeyToken = null'或其依賴項之一。該系統找不到指定的文件。 () at Scripts.Player.Player.ctor() at 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;
}
}
我已經通過代碼加強,並Inventory.cs按預期始終得到Player.cs之前編制,並正確添加到Inventory.cs.dll引用列表編譯前的Player.cs。
我必須缺少一些東西,只是簡單地鏈接引用列表中的DLL似乎不夠用,錯誤提到沒有找到文件Inventory.cs。我在哪裏指定搜索源.cs的路徑? (.cs.dll編譯的腳本始終位於與.cs源代碼腳本相同的路徑中)