An introduction to System.Reflection.Emit and OpCodes

A couple of days ago I wrote a tutorial describing how to create a proxy-class generator using System.Refleciton.Emit. After viewing the statistics of what people search for when they enter that tutorial it is clear that the problem seems to be getting dynamic functions created with System.Reflection.Emit to work with DateTime values. I had the same problem myself working with the proxy-class generator, and I thought I’d try to explain a bit. I’m no expert on this subject, but I think I’ve understood enough of it to make it useful, and I will try to tell you what I’ve found out about how the IL-code you generate should look like.

I’m also going to try to make this as practical as possible, so first let’s start by creating a console-application. And then we want to create a method that simply outputs something to the console, just to make sure that everything is working as we expected it to. But before we start writing any methods, I’ve come up with a simple extension-method that will simplify our code a little, add the following few lines to your project:

public static class MethodExtensions
{
	public static object InvokeStatic(this DynamicMethod method,
		params object[] args)
	{
		return method.Invoke(null, args);
	}
	public static T InvokeStatic<T>(this DynamicMethod method,
		params object[] args)
	{
		return (T)method.InvokeStatic(args);
	}
}

This provides a cleaner way to call the methods we create later on. But let’s get started, the first thing we want to do is simply to make our method write something to the console, just to make sure that everything works the way we expect it to. Create a method “Method1″ that returns a DynamicMethod, and write the following in it:

static DynamicMethod Method1()
{
	DynamicMethod method1 = new DynamicMethod("Method1", typeof(void),
		new Type[] { });
	ILGenerator il = method1.GetILGenerator();
	il.EmitWriteLine("Method 1 here");
	il.Emit(OpCodes.Ret);

	return method1;
}

This creates a method that calls Console.WriteLine with the parameter “Method 1 here”. This is the simplest thing you can do with the DynamicMethod, but it’s still a start. The next thing we want to do is to provide the function with a parameter specifying what to write. This isn’t much hard either, the code can look like this:

static DynamicMethod Method2()
{
	DynamicMethod method1 = new DynamicMethod("Method2", typeof(void),
		new Type[] { typeof(string) });
	ILGenerator il = method1.GetILGenerator();
	il.EmitWriteLine("Method 2 begin");

	il.Emit(OpCodes.Ldarg_0);
	il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine",
		new Type[] { typeof(string) }));

	il.EmitWriteLine("Method 2 end");
	il.Emit(OpCodes.Ret);

	return method1;
}

Notice the call to Ldarg_0. This loads the first argument onto the stack. On point to notice though, is that if this was a member-function to some class, Ldarg_0 would load the “this”-object onto the stack. But since this is a “static” function (has no this), arg_0 is the first argument passed in to the function. If you have used python (the programming language) before, this is a lot like the methods in python expects the arguments to be. After the argument is loaded onto the stack, the correct overload of “WriteLine” on the console is called.

Ok, so that’s about it for writing out strings. Also, this was the basic of passing arguments to our function, for the next example, let’s try returning a string. This isn’t hard to do either, you only need to know how it works. To return a value it needs to be the only element on the stack when you call the “ret” statement, and if you function is a void-function this will cause your program to crash, so in void-functions the stack should be empty.

To return the string passed in, simply write this:

static DynamicMethod Method3()
{
	DynamicMethod method1 = new DynamicMethod("Method3", typeof(string),
		new Type[] { });
	ILGenerator il = method1.GetILGenerator();

	il.Emit(OpCodes.Ldstr, "This is a string from method 3");
	il.Emit(OpCodes.Ret);

	return method1;
}

Thats about it for working with complex objects, and types that are passed by reference for now. As most of you probably know there exists two different types of variables in C# and in .NET in general. You have the value-types like int, float, double, etc., and you have the reference-types like string, Window, and all normal classes in general. What people probably don’t know is that a struct is also a value-type (this is the biggest difference between classes and structs if I’m not mistaken). However, a trait of the C# language (and other .NET languages too) is that everything can be passed around as an object (which is reference-type). This includes integers, floats, strings, and everything else. If you create an object-variable in C#, no matter what you try putting in it, you won’t get an error. However, to use the data-piece it either needs to be casted, or looked upon with reflection.

What most people don’t know is that when you take a value-type value and put it in an object-reference, an action is performed on the value-type variable before it is put in the object-reference. This action is called boxing. When you assign an integer or a DateTime or any other value-type value to a object-reference, boxing is done for you automatically, and you don’t really see that it’s done, and when you cast it back, it’s unboxed automatically too. However, when working with System.Reflection.Emit and OpCodes, the boxing and unboxing needs to be taken care of by the programmer (you). But before we start with that, let’s look at a simple example that takes in a DateTime, and returns it, this is fairly strait-forward since there is no object-references involved at all.

static DynamicMethod Method4()
{
	DynamicMethod method1 = new DynamicMethod("Method4", typeof(DateTime),
		new Type[] { typeof(DateTime) });
	ILGenerator il = method1.GetILGenerator();

	il.Emit(OpCodes.Ldarg_0);
	il.Emit(OpCodes.Ret);

	return method1;
}

This works as you would expect it to. You pass the function a DateTime-object and it returns it just like we wanted it to. However, what if we were to make this function into an object-function? If we replace the typeof(DateTime) (the last one) in the creation of the method, and call the method the same way you will get an error, and your code will not work. That is because when you pass the DateTime to the object-function it is automatically boxed for you, and before you can return it as a DateTime-object you need to unbox it. If you try this code however, it should work as the last example:

static DynamicMethod Method5()
{
	DynamicMethod method1 = new DynamicMethod("Method4", typeof(DateTime),
		new Type[] { typeof(object) });
	ILGenerator il = method1.GetILGenerator();

	il.Emit(OpCodes.Ldarg_0);
	il.Emit(OpCodes.Unbox_Any, typeof(DateTime));
	il.Emit(OpCodes.Ret);

	return method1;
}

And if we were to switch the parameters, so that the function accepted a DateTime and returned an object, we’dd just have to switch the Unbox_any with Box, and it would still work. As for a last example, I will provide you with a function that takes in a DateTime and prints it. The overload of the Console.WriteLine I’ll use is the WriteLine(Object) overload. The code is as following:

static DynamicMethod Method6()
{
	DynamicMethod method1 = new DynamicMethod("Method5", typeof(void),
		new Type[] { typeof(DateTime) });
	ILGenerator il = method1.GetILGenerator();

	il.EmitWriteLine("In function 6: ");
	il.Emit(OpCodes.Ldarg_0);
	il.Emit(OpCodes.Box, typeof(DateTime));
	il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine",
		new Type[] { typeof(object) }));

	il.Emit(OpCodes.Ret);

	return method1;
}

If you have any questions please leave them in the comment-section.

About these ads

8 thoughts on “An introduction to System.Reflection.Emit and OpCodes

  1. If the return value is the last one on the stack, how to you eliminate other variables that you’re using? I’m presuming that when you load a local variable, that’s loading it onto the stack, yes?

    • When you load a local variable, it’s being loaded onto the stack, yes, but I don’t quite see where I’ve left variables on the stack. Also, if you just want to get rid of the top-most variables on the stack (though I don’t see why you would load them there if you don’t need them) you can use pop.

    • The result of a method-invocation is automatically pushed to the stack. What happens when you do a method-invocation is something like this (using, say Math.Pow(int, int) (dunno if that one exists though, but it should work as an explanation)):

      [ ] <– [5]
      [5] push 2 –> [5,2]
      [5,2] Math.Pow –> [25]

      What happens here is that Math.Pow consumes the two topmost values on the stack (cause it takes two parameters), runs the Math.Pow algorithm on them and pushes the return-value on top of the stack.

  2. When should I provide typeof() parameters for OpCodes: Box, Unbox, Unbox_Any?

    Also what’s the difference between the last two?

    Also I noticed that when in Method6 i didn’t provide parameter for Box, it wasn’t done properly and the Exception was thrown; although it weren’t needed when I inverted Method5 so it would accept DateTime and return object:

    var m = new DynamicMethod(“Method5AndAHalf”, ObjectResult, DateTimeArg);
    var il = m.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Box);
    il.Emit(OpCodes.Ret);
    return m;

    The code below worked just fine.

  3. “When applied to the boxed form of a value type, the unbox.any instruction extracts the value contained within obj (of type O), and is therefore equivalent to unbox followed by ldobj.” – MSDN

    Also, according to the documentation, Box should always take a type parameter.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s