.NET 本身提供了強大的腳本引擎,可以直接使用.NET CLR的任何編程語言作爲腳本語言,如VB.NET、C#、JScript, J#等等。使用腳本引擎,我們可以動態生成任意表達式、或動態導入任意腳本文件,並在任意時候執行。
經實踐發現,我們可以使用至少兩種不同的方式在.NET中使用腳本引擎:VsaEngine和CodeDom。
其實,CodeDom不能算是真正的腳本引擎,它實際上是編譯器。但是我們完全可以利用CodeDom來模擬腳本引擎。
使用Emit方法也能達到動態生成可執行代碼的目的,而且Emit生成的代碼不需要編譯,因此速度更快。但是Emit插入的實際上是彙編代碼,不能算是腳本語言。
本文介紹如何以CodeDom方式來動態生成可執行代碼。
(Teeta無法發佈完整的文章,有興趣查看整篇文章,請到:http://ly4cn.cnblogs.com/archive/2005/11/03/267989.html)
1. 構造一個編譯器
- 設置編譯參數
編譯參數需要在CompilerParameters設置:
CompilerOptions 用於設置編譯器命令行參數 IncludeDebugInformation 用於指示是否在內存在生成Assembly GenerateInMemory 用於指示是否在內存在生成Assembly GenerateExecutable 用於指示生成的Assembly類型是exe還是dll OutputAssembly 用於指示生成的程序文件名(僅在GenerateInMemory爲false的情況) ReferencedAssemblies 用於添加引用Assembly 例如:
theParameters.ReferencedAssemblies.Add("System.dll");
- 創建指定語言的編譯器
編譯需要由指定語言的CodeDomProvider生成。
這裏列舉一些.NET的CodeDomProvider:
vb.net Microsoft.VisualBasic.VBCodeProvider C# Microsoft.CSharp.CSharpCodeProvider jscript Microsoft.JScript.JScriptCodeProvider J# Microsoft.VJSharp.VJSharpCodeProvider
以C#爲例,要創建C#編譯器,代碼如下:
ICodeCompiler compiler = new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();
下面是完整的創建編譯器的例子:
///
/// 創建相應腳本語言的編譯器
///
private void createCompiler(string strLanguage, bool debugMode, string strAssemblyFileName)
{
this.theParameters = new CompilerParameters();
this.theParameters.OutputAssembly = System.IO.Path.Combine(System.IO.Path.GetTempPath(), strAssemblyFileName + ".dll");
this.theParameters.GenerateExecutable = false;
this.theParameters.GenerateInMemory = true;
if(debugMode)
{
this.theParameters.IncludeDebugInformation = true;
this.theParameters.CompilerOptions += "/define:TRACE=1 /define:DEBUG=1 ";
}
else
{
this.theParameters.IncludeDebugInformation = false;
this.theParameters.CompilerOptions += "/define:TRACE=1 ";
}
AddReference("System.dll");
AddReference("System.Data.dll");
AddReference("System.Xml.dll");
strLanguage = strLanguage.ToLower();
CodeDomProvider theProvider;
if("visualbasic" == strLanguage || "vb" == strLanguage)
{
theProvider = new Microsoft.VisualBasic.VBCodeProvider();
if(debugMode)
theParameters.CompilerOptions += "/debug:full /optimize- /optionexplicit+ /optionstrict+ /optioncompare:text /imports:Microsoft.VisualBasic,System,System.Collections,System.Diagnostics ";
else
theParameters.CompilerOptions += "/optimize /optionexplicit+ /optionstrict+ /optioncompare:text /imports:Microsoft.VisualBasic,System,System.Collections,System.Diagnostics ";
AddReference("Microsoft.VisualBasic.dll");
}
else if("jscript" == strLanguage || "js" == strLanguage)
{
theProvider = new Microsoft.JScript.JScriptCodeProvider();
AddReference("Microsoft.JScript.dll");
}
else if("csharp" == strLanguage || "cs" == strLanguage || "c#" == strLanguage)
{
theProvider = new Microsoft.CSharp.CSharpCodeProvider();
if(!debugMode)
theParameters.CompilerOptions += "/optimize ";
}
// else if("jsharp" == strLanguage || "vj" == strLanguage || "j#" == strLanguage)
// {
// theProvider = new Microsoft.VJSharp.VJSharpCodeProvider();
// if(!debugMode)
// theParameters.CompilerOptions += "/optimize ";
// }
else
throw new System.Exception("指定的腳本語言不被支持。");
this.theCompiler = theProvider.CreateCompiler();
}
///
/// 添加引用對象。
///
/// 引用的文件名
public void AddReference(string __strAssemblyName)
{
theParameters.ReferencedAssemblies.Add(__strAssemblyName);
}
2. 編譯源代碼
編譯源代碼相當簡單,只需一條語句就搞定了:
CompilerResults compilerResults = compiler.CompileAssemblyFromSource(this.theParameters, this.SourceText);
執行後,可以從compilerResults取得以下內容:
NativeCompilerReturnValue 編譯結果,用於檢查是否成功 Errors 編譯時產生的錯誤和警告信息 CompiledAssembly 如果編譯成功,則返回編譯生成的Assembly
示例函數:///
/// 編譯腳本。編譯前將清空以前的編譯信息。
/// CompilerInfo將包含編譯時產生的錯誤信息。
///
///成功時返回True。不成功爲False。
public bool Compile()
{
this.theCompilerInfo = "";
this.isCompiled = false;
this.theCompiledAssembly = null;
this.theCompilerResults = this.theCompiler.CompileAssemblyFromSource(this.theParameters, this.SourceText);
if(this.theCompilerResults.NativeCompilerReturnValue == 0)
{
this.isCompiled = true;
this.theCompiledAssembly = this.theCompilerResults.CompiledAssembly;
}
System.Text.StringBuilder compilerInfo = new System.Text.StringBuilder();
foreach(CompilerError err in this.theCompilerResults.Errors)
{
compilerInfo.Append(err.ToString());
compilerInfo.Append("\r\n");
}
theCompilerInfo = compilerInfo.ToString();
return isCompiled;
}
3. 執行代碼
使用Reflection機制就可以很方便的執行Assembly中的代碼。
我們假設編譯時使用的腳本代碼 this.SourceText 內容如下:namespace test
{
public class script
{
static public void Main()
{
MessageBox.Show("Hello");
}
}
}則相應的執行代碼爲:
scriptEngine.Invoke("test.script", "Main", null);Invoke函數內容:
/// <summary>
/// 執行指定的腳本函數(Method)。
/// 如果指定的類或模塊名,以及函數(Method)、或參數不正確,將會產生VsaException/VshException例外。
/// </summary>
/// <param name="__strModule">類或模塊名</param>
/// <param name="__strMethod">要執行的函數(Method)名字</param>
/// <param name="__Arguments">參數(數組)</param>
/// <returns>返回執行的結果</returns>
public object Invoke(string __strModule, string __strMethod, object[] __Arguments)