Sunday, February 26, 2006
Note that my blog has moved to http://www.pksoftware.net/devblog/.

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: PKSoftware.Net.SimpleAssemblyGeneration.zip (3.77 KB)

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. Go crazy ;-)

Sunday, February 26, 2006 1:21:02 AM (GMT Standard Time, UTC+00:00) |  |  | #
Note that my blog has moved to http://www.pksoftware.net/devblog/.
Search
Links
My blog has moved to http://www.pksoftware.net/devblog/
Products
Mini SQL Query
Mini SQL Query is a minimalist SQL query tool for multiple providers (MSSQL, Oracle, OLEDB, MS Access files etc.)

Verse Popper
The "Verse Popper" displays bible verses periodically in the lower right hand corner of your screen.

Blogroll
My blog has moved to http://www.pksoftware.net/devblog/