您的位置:首页 > 其它

如何在.NET中实现脚本引擎 (CodeDom篇)

2006-01-13 14:22 357 查看
如何在.NET中实现脚本引擎 (CodeDom篇) .NET 本身提供了强大的脚本引擎,可以直接使用.NET CLR的任何编程语言作为脚本语言,如VB.NET、C#、JScript, J#等等。使用脚本引擎,我们可以动态生成任意表达式、或动态导入任意脚本文件,并在任意时候执行。
经实践发现,我们可以使用至少两种不同的方式在.NET中使用脚本引擎:VsaEngine和CodeDom。
其实,CodeDom不能算是真正的脚本引擎,它实际上是编译器。但是我们完全可以利用CodeDom来模拟脚本引擎。
使用Emit方法也能达到动态生成可执行代码的目的,而且Emit生成的代码不需要编译,因此速度更快。但是Emit插入的实际上是汇编代码,不能算是脚本语言。
本文介绍如何以CodeDom方式来动态生成可执行代码。

如何在.NET中实现脚本引擎 (CodeDom篇) 沐枫网志

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
jscriptMicrosoft.JScript.JScriptCodeProvider
J#Microsoft.VJSharp.VJSharpCodeProvider
以C#为例,要创建C#编译器,代码如下:

//.NET 1.1/1.0


ICodeCompiler compiler = new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();


//.NET 2.0


ICodeCompiler compiler = (ICodeCompiler) new Microsoft.CSharp.CSharpCodeProvider();




下面是完整的创建编译器的例子:



/**//// <summary>


/// 创建相应脚本语言的编译器


/// </summary>


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();


}






/**//// <summary>


/// 添加引用对象。


/// </summary>


/// <param name="__strAssemblyName">引用的文件名</param>


public void AddReference(string __strAssemblyName)






{


theParameters.ReferencedAssemblies.Add(__strAssemblyName);


}




在.NET Framework 2.0中,由于CreateCompiler方法被标记作废。为避免产生编译警告,可直接返回CodeDomProvider作为编译器:

this.theCompiler = (ICodeCompiler)theProvider;



2. 编译源代码

编译源代码相当简单,只需一条语句就搞定了:

CompilerResults compilerResults = compiler.CompileAssemblyFromSource(this.theParameters, this.SourceText); 执行后,可以从compilerResults取得以下内容:
NativeCompilerReturnValue 编译结果,用于检查是否成功
Errors 编译时产生的错误和警告信息
CompiledAssembly 如果编译成功,则返回编译生成的Assembly
示例函数:



/**//// <summary>


/// 编译脚本。编译前将清空以前的编译信息。


/// CompilerInfo将包含编译时产生的错误信息。


/// </summary>


/// <returns>成功时返回True。不成功为False。</returns>


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)






{


if(!this.IsCompiled || this.theCompiledAssembly == null)


throw new System.Exception("脚本还没有成功编译");




Type __ModuleType = this.theCompiledAssembly.GetType(__strModule);


if(null == __ModuleType)


throw new System.Exception(string.Format("指定的类或模块 ({0}) 未定义。", __strModule));




MethodInfo __MethodInfo = __ModuleType.GetMethod(__strMethod);


if(null == __MethodInfo)


throw new System.Exception(string.Format("指定的方法 ({0}::{1}) 未定义。", __strModule, __strMethod));




try






{


return __MethodInfo.Invoke(null, __Arguments);


}


catch( TargetParameterCountException )






{


throw new System.Exception(string.Format("指定的方法 ({0}:{1}) 参数错误。", __strModule, __strMethod));


}


catch(System.Exception e)






{


System.Diagnostics.Trace.WriteLine(string.Format("执行({0}:{1})错误: {2}", __strModule, __strMethod, e.ToString()));


return null;


}


}



总结: CodeDom可以很方便的随时编译源代码,并动态执行。虽然作为脚本引擎,它没有VsaEngine正规和方便,但作为一般应用,也够用了。并且结合Reflection机制,它的功能比VsaEngine更强大:它可以编译任何提供CompilerProvider的CLR语言(目前.NET自带的语言中都有)。
当然,它也有一些缺点:它生成的Assembly不能动态卸载。这在一般情况下不成问题,因为一个源代码只需编译一次,并载入执行,并不需要动态卸载。
假如你需要做脚本编辑器时,就要考虑这个问题,因为有可能一个脚本会因为修修改改而不停的重新编译,从而造成不停的产生新的Assembly,最后将导致内存被大量占用。要解决这个问题,需要将编译器加载到独立的AppDomain中,通过卸载AppDomain达到卸载所需的Assembly的目的。 附件为完整的源代码,以供测试:
http://www.cnblogs.com/Files/ly4cn/netScript.rar
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: