Creating .Net Assemblies on the fly - Using CodeDom

Generating .net assemblies from within .net isn't so hard. Here is an example, I need to flesh it out a fair bit but its pretty easy to follow.

I have used this approach for generating code in some of my apps.

Basic Code Compilation Helper Function

/// <summary>
/// Creates an executable file from the supplied <paramref name="code"/>.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="referencedAssemblies">The referenced assemblies (apart from "system.dll").</param>
/// <param name="exeName">Name of the exe, e.g. "HelloWorld".</param>
/// <returns>The path of the new exe.</returns>
public static string CreateExe(string code, string mainClass, string[] referencedAssemblies, string exeName)
{
    // Create an instance of the CSharpCodeProvider to compile the C# code
    CodeDomProvider codeProvider = new CSharpCodeProvider();

    //create the language specific code compiler
    //ICodeCompiler compiler = codeProvider.CreateCompiler();
    
    //add compiler parameters
    CompilerParameters compilerParams = new CompilerParameters();
    compilerParams.GenerateExecutable = true; // .exe
    compilerParams.OutputAssembly = exeName + ".exe";
    compilerParams.CompilerOptions = "/optimize";
    compilerParams.GenerateInMemory = false; // will get written to disk
    compilerParams.IncludeDebugInformation = false; // i.e. release code
    compilerParams.TempFiles = new TempFileCollection(".", true);
    compilerParams.MainClass = mainClass;
    compilerParams.ReferencedAssemblies.Add("system.dll");

    if (referencedAssemblies != null)
    {
        foreach (string reference in referencedAssemblies)
        {
            compilerParams.ReferencedAssemblies.Add(reference);
        }
    }

    //actually compile the code
    CompilerResults results = codeProvider.CompileAssemblyFromSource(compilerParams, code);

    //Do we have any compiler errors
    if (results.Errors.Count > 0)
    {
        string errors = string.Empty;
        foreach (CompilerError error in results.Errors)
            errors += error.ToString() + Environment.NewLine;
        throw new Exception(errors);
    }

    return results.PathToAssembly;
}

Calling the Helper Function

Call the function in a method similar to:

static void Main(string[] args)
{
    // I appoligize in advance for this program:
    string sourceCode = @"
using System;

namespace HelloWorld
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(""Sorry, but hello world..."");
        }
    }
}"
;

    string path = AssemblyUtility.CreateExe(
       sourceCode, "HelloWorld.Program", null, "HelloWorld");

    Console.WriteLine("New exe at: " + path);
}

Basically this will create an executable file called "HelloWorld.exe" (I hate that example!).

Examining the Results

In the code above I chose to leave the temporary files in the build directory [compilerParams.TempFiles = new TempFileCollection(".", true)]. If you run the sample application you will fild the following files created:

  • 2ng5ojw-.0.cs - contains the source code to compile
  • 2ng5ojw-.cmdline - the commandline options passed to the C# compiler
  • 2ng5ojw-.err - nothing in this case but error text if we encountered any
  • 2ng5ojw-.out - console output from the C# compiler
  • 2ng5ojw-.tmp - an (empty) temp file
  • HelloWorld.exe - the resulting executable

Example Source Code: (TODO)

So Whats So Good About That?

Not so hard? Why do I need the CSharpCodeProvider etc? The real power of code compilation comes when you start building DLL's in memory [compilerParams.GenerateInMemory = true]. You can then get the resulting compiled Assembly at runtime from the compiler result [results.CompiledAssembly].

Using an Assembly thats been Generated on the Fly

If you generate a new assembly on the fly you can use it one of 2 ways (or you can save it to disk and link to it etc.)

Method 1 - Use reflection. Upside, extreemly flexible. Downside, slow.

Method 2 - Have your generated code implement an interface or inherit from a base clase that the running code already knows about. This is my prefered method. Its far more effecient. How?

BaseClass CreateClassInstance(Assembly assemblyReference, string className)
{
    return assemblyReference.CreateInstance(className) as BaseClass;
}

When I get around to it I will post some updated code for these extentions.