Sunday, May 2, 2010

Assembly on a fly

Recently I was on a project that needed me to create assemblies on a fly from source code contained on the disk in simple txt files.
The way to go was CodeDom way.

This blog is all about CodeDom. We will see how to generate the assembly in memory from a simple .cs/txt file containing n number of classes.
Lets for example we have simple .cs file having a single class.


Class MyClass
{

public int MyId
{
get;
set;

}

public string MyName
{
get;
set;
}

public string MySSNO
{
get;
set;

}

public MyClass(int id, string name, string ssno)
{
this.MyId = id;
this.MyName = name;
this.MySSNo = ssno;

}

}


We have this class in .cs file on disk.

Namespaces to use for in memory assembly creation are:-

using System.CodeDom.Compiler;
using System.CodeDom;
using Microsoft.CSharp;


These namespaces contains the classes that we will use to compile our code and create in memory assembly.

Lets start with initializing these classes.


CodeDomProvider provider = new
Microsoft.CSharp.CSharpCodeProvider();
ICodeCompiler compiler = provider.CreateCompiler();
CompilerParameters parms = new CompilerParameters();


Now we will set the parameters for the compiler.

parms.GenerateExecutable = false;
parms.GenerateInMemory = true;
parms.IncludeDebugInformation = false;
parms.CompilerOptions = String.Format("/lib:\"{0}\"",
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Reference Assemblies\Microsoft\Framework\v3.0")) + String.Format(" /lib:\"{0}\"",
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Reference Assemblies\Microsoft\Framework\v3.5"));


If there are any assemblies that our code references we can add them as:-

parms.ReferencedAssemblies.Add("PresentationCore.dll");
parms.ReferencedAssemblies.Add("PresentationFramework.dll");
parms.ReferencedAssemblies.Add("System.dll");
parms.ReferencedAssemblies.Add("System.configuration.dll");
parms.ReferencedAssemblies.Add("System.Core.dll");
parms.ReferencedAssemblies.Add("System.Drawing.dll");
parms.ReferencedAssemblies.Add("System.Data.dll");
parms.ReferencedAssemblies.Add("System.XML.dll");
parms.ReferencedAssemblies.Add("WindowsBase.dll");


Next step is to read the .cs file content, i.e our source code.

string fileContent = string.Empty;
StreamReader reader = null;

using (FileStream csFileStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read))
{
reader = new StreamReader(csFileStream);
fileContent = reader.ReadToEnd();
reader.Close();
}


After reading our source code as string , we will create a compile unit for out source code and compile it .

CodeCompileUnit compileUnit = new CodeSnippetCompileUnit
(fileContent);
CompilerResults results = compiler.CompileAssemblyFromDom(parms,
compileUnit);


We will check for any errors that came during the compilation.

//Report errors if any.
if (results.Errors.Count > 0)
{
CompilerErrorCollection errors = results.Errors;

for (int i = 0; i < errors.Count; i++ )
{
Console.WriteLine(errors[i].ErrorText.ToString());
}
}


if there are no errors during compilation , we have got our in memory assembly.

Assembly myIMemoryAssembly = results.CompiledAssembly;

4 comments:

  1. Very good article! I'm migrating some web techniques of a little PHP framework to ASP.net and this will be very useful for me. This is also useful if you want to obfuscate a DLL code. Thank you!

    ReplyDelete
  2. Thank you. That helped a lot. No I am testing under Windows 8 and this does not work. I changed the "parms.CompilerOptions" with "lib: \Microsoft.NET\Framework\v4.0.30319\WPF" where is the OS Location (usually: C:\Windows) which seems to solve the Problem.

    Cheers
    Sascha

    ReplyDelete
  3. Sorry, I forgot: Tested under Windows XP Pro SP3 (32)/Vista (32)/7(32/64)/8(32).

    Cheers
    Sascha

    ReplyDelete