65.9K
CodeProject 正在变化。 阅读更多。
Home

运行时编译代码

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (22投票s)

2005年5月9日

3分钟阅读

viewsIcon

171481

downloadIcon

4348

本文介绍如何在运行时编译代码。

引言

在某些情况下,在执行期间编译源代码非常有用。对于这种情况,可以使用命名空间 Microsoft.CSharp。这个命名空间包含类 CSharpCodeProvider。使用 CSharpCodeProvider,您可以访问 C# 编译器。请注意,您也可以将编译器与 VB.NET 一起使用,只需使用命名空间 Microsoft.VisualBasic 和类 VBCodeProvider。此外,为了能够在运行时编译您的代码,您必须添加 System.CodeDomSystem.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 - 添加了如何捕获错误。
© . All rights reserved.