Thursday, October 20, 2005 10:28 PM bart

Reflection.Emit explained

I intended to blog about this long time ago, but the idea died while sitting in the long long blog idea queue. Now it's here: a post about System.Reflection.Emit. Stimulans: a session on writing a managed compiler in one hour on PDC 05. Let's kick off.

 

What is it?

System.Reflection.Emit is a namespace that allows assemblies to be created from within managed code applications. Once you've created the assembly dynamically it can be called immediately or it can be saved to disk to be executed later on. In fact, you can use System.Reflection.Emit for two big scenarios:

  • compiling stuff to managed code and call that code at runtime of your application (e.g. give the user the opportunity to write logic statements or code statement - or whatever you might think of - and compile and execute it)
  • create a managed code compiler that accepts a source code file for language ABC and spits out an assembly containing the corresponding IL code

Noticed the word IL? Yes, there we go again :-).

 

Hello world +o(

Sick and tired of those hello world samples? I do, so let's create a "Yow dude" example instead (pretending to be in a funny mood).

Manual compilation

Open up a Visual Studio command prompt (e.g. VS .NET 2003, will work with version 2005 too). Write and compile a one line application as follows:

>echo class Yow { public static void Main() { System.Console.WriteLine("Yow dude"); } } > yow.cs

>csc yow.cs
Microsoft (R) Visual C# .NET Compiler version 7.10.6310.4
for Microsoft (R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.

Execute yow.exe if you doubt about the results of this application (if so, I'd recommend to quit your browser now and come back next year :o). Now ildasm.exe yow.exe and take a look at Yow::Main's definition. It should not just look like but be completely identical to the next piece of IL:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       11 (0xb)
  .maxstack  1
  IL_0000:  ldstr      "Yow dude"
  IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000a:  ret
} // end of method Yow::Main

 

Dynamic compilation

Now we're going to write an application that creates the same tremendously useful (*kuch*) functionality but dynamically using Reflection.Emit. Open up your favorite development environment to create a console application and compile it. One thing missing in the previous fase? Yes, the code. So here it is:

using System; using System.Reflection; using System.Reflection.Emit;

 class ReflectSample
 {
  static void Main(string[] args)
  {
   AssemblyName name = new AssemblyName();
   name.Name = "YowAgain";

   AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
   ModuleBuilder module = assembly.DefineDynamicModule("YowModule", "yowagain.exe");
   
   TypeBuilder someClass= module.DefineType("Yow", TypeAttributes.Public | TypeAttributes.Class);
   MethodBuilder main = someClass.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] {});

   ILGenerator ilgen = main.GetILGenerator();
   ilgen.Emit(OpCodes.Ldstr, "Yow dude");
   ilgen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
   ilgen.Emit(OpCodes.Ret);

   assembly.SetEntryPoint(someClass.CreateType().GetMethod("Main"), PEFileKinds.ConsoleApplication);
   assembly.Save(module.Name);

   AppDomain.CurrentDomain.ExecuteAssembly(module.Name);
  }
 }

Line by line overview? Here it comes (blank and curly-brace-only lines were skipped):

  1. Some namespace imports. Mandatory plumbing to boost readability of what comes next.
  2. Our class.
  3. Our entrypoint.
  4. Creating an AssemblyName instance for the assembly we're going to create.
  5. and give it a meaningful name.
  6. Next, we need something to build an assembly dynamically, which the System.Reflection.Emit team decided to call - for unclear reasons - AssemblyBuilder. To create it and execute it later on, it has to be created through the (current) application domain. We also indicate the result has to be saved and executed.
  7. Similarly, a module - that will be acting as part of the assembly - needs to be created. Straightforward chaining to the AssemblyBuilder instance encountered above.
  8. Modules contain types. Chain again, this time with a TypeBuilder. The rest of the line should read like a boring novel you can find in the local library: "define a public class type called Yow".
  9. Types contain methods. Or what predictability means in the context of Reflection.Emit: "define a public static method called Main that returns void and takes no arguments".
  10. Woohoo, ILGenerator, that sounds promising. This is where the real work is done. For the Main method, a utility class is instantiated to write the code for the method dynamically.
  11. See our IL sample, line IL_0000.
  12. See our IL sample, line IL_0005. Extra parameters extract MethodInfo through reflection (i.e. getting method invocation information for the overload of System.Console::WriteLine with one string parameter).
  13. See our IL sample, line IL_000a.
  14. Exciting things are said and done. The entry-point is set on the assembly to point to our Main method we defined above. Notice that reflection is used in here, which indicates the code (type) is "loaded" already and metadata for it is available.
  15. The assembly is saved to disk.
  16. Finally, we also decide to run it at this very moment. As you can see, the code you've created is in the current assembly domain (see line 6 too), which also means it can't be unloaded without unloading the default app domain (and therefore exiting the process).

Run. "Yow dude" should appear and a file yow_dynamic.exe should be created. Finally, the IL looks like this:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       11 (0xb)
  .maxstack  1
  IL_0000:  ldstr      "Yow dude"
  IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000a:  ret
} // end of method Yow::Main

Exactly the same as the result of our manual development and compilation earlier. Certainly worth to look further at System.Reflection.Emit (tip: don't try to understand all of the OpCodes that are available, start from an existing piece of IL-code).

Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Comments

No Comments