Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Compiling code during runtime

0.00/5 (No votes)
8 May 2005 1  
This article shows how to compile code during runtime.

Introduction

In some cases it is very useful to compile the source code during execution. For this situation you can use the namespace Microsoft.CSharp. This namespace contains the class CSharpCodeProvider. Using the CSharpCodeProvider you can have access to the C#-compiler. Please note that you can use the compiler with VB.NET also, just use the namespace Microsoft.VisualBasic and the class VBCodeProvider. In Addition to be able to compile your code on the fly you have to add the System.CodeDom and the System.CodeDom.Compiler namespaces.

First step

In this example we will use a RichTextBox in which we'll load some example-code. After that you will be able to compile the code. In this step we'll have a closer look at the source code of the application:

CSharpCodeProvider csp = new CSharpCodeProvider();
ICodeCompiler cc = csp.CreateCompiler();

Using CSharpCodeProvider we gain access to the compiler. Using this compiler we are able to use the csc.exe without any need for the command line. To compile the example-code successfully we have to set some compiler parameters. They include which assemblies should be referred to, where the output assembly has to be stored, which warning level we want to use and some other stuff. The compiler options that we have set are the same as the compiler options you have set using the 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;

The CompilerParameters class has a property GenerateInMemory. We set this to false because we want to write the assembly to disk and not to memory.

The compilation

After the compilation process we want to know the results, so we create a new instance of the class CompilerResults. CompilerResults needs the parameter tempFiles. These are the files that will be generated by the compiler during compilation. Create an instance of TempFileCollection and set the path to the location where we create the temporary files. If you want to keep the files after compilation set the second parameter of the TempFileCollection to true, otherwise false:

System.CodeDom.Compiler.TempFileCollection tfc = 
                new TempFileCollection(Application.StartupPath, false);
CompilerResults cr  = new CompilerResults(tfc);

Now we can compile our example code. There are different possibilities that are given by the CodeCompiler. In our case we use the method CompileAssemblyFromSource:

cr = cc.CompileAssemblyFromSource(cp, this.rtfCode.Text);
System.Collections.Specialized.StringCollection sc = cr.Output;
foreach (string s in sc) 
{
    Console.WriteLine(s);
}

After compilation we write the created output to the console. In some cases there may be some compiling errors. You can catch them looking at the property Errors of the CompilerResults object:

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;
}

The execution

Now we've compiled our example code and want to execute it. As you can see within the source code of the sample application, I have created an interface called Command, which includes the method Execute(). Our example code is the implementation of this interface.

To execute the assembly we have to create a new application domain in which the assembly should be loaded. So that we are able to unload the assembly. We use ShadowCopyFiles, because of this we are able to override the original assembly:

AppDomainSetup ads = new AppDomainSetup();
ads.ShadowCopyFiles = "true";
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain newDomain = AppDomain.CreateDomain("newDomain");

We've created the new application domain, and now we want to load the assembly by doing the following lines:

byte[] rawAssembly = loadFile("TestClass.dll");
Assembly assembly = newDomain.Load(rawAssembly, null);

OK, the assembly is loaded, now we want to execute the sample code which will show us a simple MessageBox:

Command testClass = 
        (Command)assembly.CreateInstance("CSharpScripter.TestClass");
testClass.Execute();

After showing the MessageBox we will unload the assembly and the application domain:

testClass = null;
assembly = null;
AppDomain.Unload(newDomain);
newDomain = null;

History

  • 2005/09/05 - Initial article.
  • 2005/10/05 - Added how to catch errors.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here