Click here to Skip to main content
15,868,054 members
Articles / Programming Languages / MSIL

Generating Assemblies at runtime using IL emit

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
2 Jul 2012CPOL3 min read 14K   7   2
How to generate assemblies at runtime using IL emit

Let’s start with a bold statement: IL rocks! Fair enough, but can it do Jazz or Mozart too? Turns out, yes it can :)

While thinking of a way to magically create an instance out of an interface for my little MData project, I figured I’d have to go with IL generating such a class. It seemed like a daunting task at first. Really, look at this code:

MSIL
Offset OpCode 
0 ldnull 
1 ldarg.0 
2 ldc.i4.2 
3 newarr System.Object
8 stloc.0 
9 ldloc.0 
10 ldc.i4.0 
11 ldarg.1 
12 stelem.ref 
13 ldloc.0 
14 ldc.i4.1 
15 ldarg.2 
16 stelem.ref 
17 ldloc.0 
18 call System.String System.String::Format(System.IFormatProvider,System.String,System.Object[])
23 ret

Can you guess which method this is? Probably not, well here it is:

C#
public static string Format(string format, object arg0, object arg1)
{
    return Format(null, format, new object[] { arg0, arg1 });
}

If you’ve never really looked into IL (or MSIL), this code may seem very hard to understand. Very handy tools to look at the IL code of a given .NET method, are ILSpy (free), Reflector (in combination with Reflexil) or Graywolf (free). These tools let you compare the classic C#/VB.NET code to the compiled IL code. This is the best way to ‘learn’ IL.

Generating a Dynamic Assembly at Runtime

This goes just one step further than just looking at IL code, this is actually writing IL code. While it is not possible to alter existing Assemblies (not in my knowledge), it is certainly possible to create a new one. This is used in several popular frameworks which support creating proxy classes, ducktyping, etc. The key here is to *not* use the default System.Reflection.Emit namespace :) . I personally don’t like the structure and approach in the standard BCL, instead I love to use BLToolkit from Igor Tkachev. It allows me to create a ‘Hello world’ example using a very natural flow:

MSIL
EmitHelper emit = new AssemblyBuilderHelper("HelloWorld.dll")
.DefineType ("Hello", typeof(object), typeof(IHello))
.DefineMethod(typeof(IHello).GetMethod("SayHello"))
.Emitter;

emit
// string.Format("Hello, {0}!", toWhom)
//
.ldstr ("Hello, {0}!")
.ldarg_1
.call (typeof(string), "Format", typeof(string), typeof(object))

// Console.WriteLine("Hello, World!");
//
.call (typeof(Console), "WriteLine", typeof(string))
.ret()
;

And that’s it, we created a new assembly with one type containing one method. As you can see we can define a baseclass for the type, we could also add which interfaces to implement, etc.

TIP: To put a certain class in a namespace, define the name as follows: Path.Of.Your.Namespace.[GeneratedTypeName], this will trigger the emit system to extract the namespace from the class name.

Now let’s try to make a working example of a ducktyping system. the purpose of our little library will be to cast any given class to a interface it doesn’t explicitly implement. Something like this:

C#
class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        ITest aAsITest = a.Duck<ITest>();
    }
}

public class A
{
    string Data { get; set; }

    public void Method()
    {
            
    }
}
    
public interface ITest
{
    string Data { get; set; }
    void Method();
}

Notice how class A does not implement ITest, after ‘ducking’ however, I can have an instance of ITest working on the A instance. The first step would be creating some kind of workflow and scope for this project:

  • The targetType should have all members of the interface defined as public
  • We will not take into account that only a subset of members is implemented by the targetType, so only one on one matches
  • We will map methods, properties and events

First step is actually creating an ‘AssemblyBuilder’ from the BLToolkit:

C#
static ClassFactory()
{
    _assemblyBuilder = new AssemblyBuilderHelper(GetGeneratedAssemblyPath());
}

private static string GetGeneratedAssemblyPath()
{
    return @".\Duck.Tape.Generated.dll";
}

That was easy! Now here is the flow of creating all methods, properties and event on the newly generated type:

C#
var typeBuilder = _assemblyBuilder.DefineType(GetGeneratedTypeName(),  
                  typeof(object), InterfaceToImplement);

//create private field
var classToWrapField = typeBuilder.DefineField(GetGeneratedToWrapFieldName(), 
                                               ClassToWrap, FieldAttributes.Private);

//map interface members
MapInterfaceProperties(classToWrapField, typeBuilder);
MapInterfaceEvents(classToWrapField, typeBuilder);
MapInterfaceMethods(classToWrapField, typeBuilder);

//define constructor
DefineConstructor(classToWrapField, typeBuilder);

As you can see, we have a clear overview of what is happening during the creation of our new Type. This is very important when dealing with the whole Emit thing. Keep things as simple as possible, and please don’t create methods larger than 20-25 lines. It can get real messy, real fast.

Now for some IL magic, let’s look at how the constructor is defined:

C#
private void DefineConstructor(FieldInfo classToWrapField, TypeBuilderHelper typeBuilder)
{
    typeBuilder
        //take an instance of 'ClassToWrap' as constructor parameter
        .DefinePublicConstructor(ClassToWrap)
        .Emitter
        //: base()
        .ldarg_0
        .call(typeof(object).GetConstructors()[0])
        //this.[fieldName] = [constructorParameter]
        .ldarg_0
        .ldarg_1
        .stfld(classToWrapField)
        //return
        .ret();
}

If you are interested in this library, please download/fork it here!

Please don’t send me e-mails that it doesn’t work for you, just open an issue on github, or create a pull request and solve it yourself ;)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Belgium Belgium
LinkedIn Profile

I maintain a blog at pietervp.com

Comments and Discussions

 
GeneralFormatting Issues Pin
Tim Corey2-Jul-12 2:50
professionalTim Corey2-Jul-12 2:50 
GeneralRe: Formatting Issues Pin
Pieter Van Parys2-Jul-12 2:59
Pieter Van Parys2-Jul-12 2:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.