运行时编译代码






4.71/5 (22投票s)
2005年5月9日
3分钟阅读

171481

4348
本文介绍如何在运行时编译代码。
引言
在某些情况下,在执行期间编译源代码非常有用。对于这种情况,可以使用命名空间 Microsoft.CSharp
。这个命名空间包含类 CSharpCodeProvider
。使用 CSharpCodeProvider
,您可以访问 C# 编译器。请注意,您也可以将编译器与 VB.NET 一起使用,只需使用命名空间 Microsoft.VisualBasic
和类 VBCodeProvider
。此外,为了能够在运行时编译您的代码,您必须添加 System.CodeDom
和 System.CodeDom.Compiler
命名空间。
第一步
在本例中,我们将使用一个 RichTextBox
,在其中加载一些示例代码。之后,您将能够编译代码。在这一步中,我们将仔细查看应用程序的源代码
CSharpCodeProvider csp = new CSharpCodeProvider();
ICodeCompiler cc = csp.CreateCompiler();
使用 CSharpCodeProvider
,我们可以访问编译器。使用这个编译器,我们能够使用 csc.exe,而不需要命令行。为了成功编译示例代码,我们必须设置一些编译器参数。它们包括应该引用哪些程序集,输出程序集必须存储在哪里,我们想要使用的警告级别以及其他一些东西。我们设置的编译器选项与您使用 csc.exe 设置的编译器选项相同
CompilerParameters cp = new CompilerParameters();
cp.OutputAssembly = Application.StartupPath + "\\TestClass.dll";
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");
cp.ReferencedAssemblies.Add("CSharpScripter.exe");
cp.WarningLevel = 3;
cp.CompilerOptions = "/target:library /optimize";
cp.GenerateExecutable = false;
cp.GenerateInMemory = false;
CompilerParameters
类有一个属性 GenerateInMemory
。我们将其设置为 false
,因为我们希望将程序集写入磁盘而不是内存。
编译
编译过程之后,我们想知道结果,所以我们创建了 CompilerResults
类的一个新实例。CompilerResults
需要参数 tempFiles
。这些是编译器在编译期间生成的文件。创建 TempFileCollection
的实例,并将路径设置为创建临时文件的位置。如果您想在编译后保留文件,请将 TempFileCollection
的第二个参数设置为 true
,否则设置为 false
。
System.CodeDom.Compiler.TempFileCollection tfc =
new TempFileCollection(Application.StartupPath, false);
CompilerResults cr = new CompilerResults(tfc);
现在我们可以编译我们的示例代码了。CodeCompiler 提供了不同的可能性。在本例中,我们使用方法 CompileAssemblyFromSource
cr = cc.CompileAssemblyFromSource(cp, this.rtfCode.Text);
System.Collections.Specialized.StringCollection sc = cr.Output;
foreach (string s in sc)
{
Console.WriteLine(s);
}
编译之后,我们将创建的输出写入控制台。在某些情况下,可能会出现一些编译错误。您可以通过查看 CompilerResults
对象的 Errors
属性来捕获它们
if (cr.Errors.Count > 0)
{
foreach (CompilerError ce in cr.Errors)
{
Console.WriteLine(ce.ErrorNumber + ": " + ce.ErrorText);
}
MessageBox.Show(this, "Errors occoured", "Errors",
MessageBoxButtons.OK, MessageBoxIcon.Error);
this.btnExecute.Enabled = false;
}
else
{
this.btnExecute.Enabled = true;
}
执行
现在我们已经编译了我们的示例代码,并且想要执行它。正如您在示例应用程序的源代码中看到的那样,我创建了一个名为 Command
的接口,它包含方法 Execute()
。我们的示例代码是这个接口的实现。
要执行程序集,我们必须创建一个新的应用程序域,程序集将在其中加载。以便我们可以卸载程序集。我们使用 ShadowCopyFiles
,因此我们能够覆盖原始程序集
AppDomainSetup ads = new AppDomainSetup();
ads.ShadowCopyFiles = "true";
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain newDomain = AppDomain.CreateDomain("newDomain");
我们已经创建了新的应用程序域,现在我们想通过执行以下行来加载程序集
byte[] rawAssembly = loadFile("TestClass.dll");
Assembly assembly = newDomain.Load(rawAssembly, null);
好的,程序集已加载,现在我们想执行示例代码,它将向我们显示一个简单的 MessageBox
Command testClass =
(Command)assembly.CreateInstance("CSharpScripter.TestClass");
testClass.Execute();
在显示 MessageBox
之后,我们将卸载程序集和应用程序域
testClass = null;
assembly = null;
AppDomain.Unload(newDomain);
newDomain = null;
历史
- 2005/09/05 - 初始文章。
- 2005/10/05 - 添加了如何捕获错误。