Home > Articles > Programming > Windows Programming

  • Print
  • + Share This
From the author of

Creating a "Hello World" Emitter

To keep from writing a book here, I've kept the example program short. This program emits an assembly that's the equivalent of a "Hello, World" program. It demonstrates the basics of writing an emitter and is practicable in the space we have remaining.

Let's jump right into the code. Listing 1 demonstrates a single instance of the code we want to approximate in MSIL. Listing 2 demonstrates the emitter that will generate the MSIL that in turn will create a single assembly named Hello.exe that will finally write to the console whatever text we create the emitter with.

Listing 1—A Simple Hello, World! Application

using System;

namespace Hello
{
 class Class1
 {
  [STAThread]
  static void Main(string[] args)
  {
   Console.WriteLine("Hello, World");
  }
 }
}

Listing 1 demonstrates a basic sample console application that can probably be found in every .NET book available. The most interesting thing about this sample program is that it demonstrates how easy it is to create console applications, which are still useful for utility applications.

Listing 2 demonstrates the C# code necessary to dynamically generate an application identical to the assembly that will be created from Listing 1. The first thing you'll notice is that the emitter is an order of magnitude longer. (That is, it's at least ten times longer than the C# version it emulates.)

Listing 2—An Emitter That Generates a Vanilla Hello.exe Application

1: using System;
2: using System.Reflection;
3: using System.Reflection.Emit;
4:
5: namespace HelloWorldEmitter
6: {
7:  class EmitterDemo
8:  {
9:   [STAThread]
10:   static void Main(string[] args)
11:   {
12:    Usage(args);
13:    System.Diagnostics.Debug.Assert(args.Length == 1);
14:    Emitter.Run(args[0]);
15:
16:   }
17:
18:   private static void Usage(string[] args)
19:   {
20:    if( args.Length == 1 ) return;
21:    const string usage =
22:    "HelloWorld <text echo to the console>\r\n" +
23:    "(e.g. HelloWorld \"Hello World!\"\r\n" +
24:    "will emit a dynamic assembly that displays\r\n" +
25:    "the parameter text.)\r\n\r\n" +
26:    "Copyright \xa9 2002. All Rights Reserved.\r\n" +
27:    "by Paul Kimmel. pkimmel@softconcepts.com\r\n";
28:
29:    Console.WriteLine(usage);
30:    Console.ReadLine();
31:
32:   }
33:  }
34:
35:  public class Emitter
36:  {
37:   public static void Run(string toEmit)
38:   {
39:    Emitter emitter = new Emitter(toEmit);
40:    emitter.Emit();
41:   }
42:
43:   private string toEmit;
44:   private AssemblyName assemblyName;
45:   private AssemblyBuilder assemblyBuilder;
46:   private ModuleBuilder moduleBuilder;
47:   private TypeBuilder typeBuilder;
48:   private MethodBuilder methodBuilder;
49:
50:   protected Emitter(string toEmit)
51:   {
52:    this.toEmit = toEmit;
53:   }
54:
55:   public void Emit()
56:   {
57:    const string name = "Hello.exe";
58:    assemblyName = new AssemblyName();
59:    assemblyName.Name = "Hello";
60:
61:    // Define dynamic assembly
62:    assemblyBuilder = CreateAssemblyBuilder(assemblyName,
63:    AssemblyBuilderAccess.Save);
64:
65:    // Define dynamic module
66:    moduleBuilder = CreateModuleBuilder(assemblyBuilder);
67:
68:    // Define dynamic type
69:    typeBuilder = CreateTypeBuilder(moduleBuilder);
70:
71:    // Define dynamic method
72:    methodBuilder = CreateMethodBuilder(typeBuilder);
73:
74:    // Apply attributes
75:    methodBuilder.SetCustomAttribute( CreateAttributeBuilder());
76:
77:    // Establish entry point Main
78:    assemblyBuilder.SetEntryPoint(methodBuilder);
79:
80:    // Write the lines of code
81:    EmitCode(methodBuilder, toEmit);
82:
83:    // Create the type
84:    typeBuilder.CreateType();
85:
86:    // Save the dynamic assembly
87:    assemblyBuilder.Save(name);
88:   }
89:
90:   private AssemblyBuilder CreateAssemblyBuilder(
91:    AssemblyName aName, AssemblyBuilderAccess access)
92:   {
93:    return AppDomain.CurrentDomain
94:     .DefineDynamicAssembly(aName, access);
95:   }
96:
97:   private ModuleBuilder CreateModuleBuilder(
98:    AssemblyBuilder builder)
99:   {
100:   return assemblyBuilder.DefineDynamicModule("Class1.mod",
101:    "Hello.exe", false);
102:  }
103:
104:  private TypeBuilder CreateTypeBuilder(ModuleBuilder builder)
105:  {
106:   return builder.DefineType("Class1");
107:  }
108:
109:  private MethodBuilder CreateMethodBuilder(TypeBuilder builder)
110:  {
111:   return builder.DefineMethod("Main",
112:    MethodAttributes.Private | MethodAttributes.Static
113:    | MethodAttributes.HideBySig,
114:    CallingConventions.Standard, typeof(void),
115:    new Type[]{typeof(string[])});
116:  }
117:
118:  private CustomAttributeBuilder CreateAttributeBuilder()
119:  {
120:   return new CustomAttributeBuilder(
121:    typeof(System.STAThreadAttribute).GetConstructor(new Type[]{}),
122:    new object[]{});
123:  }
124:
125:  private void EmitCode(MethodBuilder builder, string text)
126:  {
127:   ILGenerator generator = builder.GetILGenerator();
128:   generator.Emit(OpCodes.Ldstr, text);
129:
130:   MethodInfo methodInfo = typeof(System.Console).GetMethod(
131:    "WriteLine",
132:    BindingFlags.Public | BindingFlags.Static, null,
133:    new Type[]{typeof(string)}, null);
134:
135:   generator.Emit(OpCodes.Call, methodInfo);
136:   generator.Emit(OpCodes.Ret);
137:  }
138: }
139: }

Lines 7 through 33 implement the EmitterDemo class, which is the entry point for this sample application. The EmitterDemo class makes sure that one string is passed to the console application and invokes Emitter.Run method.

The rest of the code implements the Emitter class, which generates the dynamic Hello.exe assembly. Emitter.Run creates an instance of the Emitter class and invokes the Emit method. The Emit method was written very deliberately. If you follow the Emit method from the first statement (line 55) to the last (line 88), you can get an idea of the macro steps for emitting a dynamic assembly. (The steps in the previous section describe the macro, beginning with creating an AssemblyBuilder and ending after you've emitted that last member, demonstrated on lines 62 and 81, respectively.) Finally, we save the assembly on line 87, which writes the assembly to disk.

If you want to run the assembly in memory, you can define the dynamic assembly with AssemblyBuilderAccess.Run. You don't have to write an assembly to disk to use it. You create an instance of the type in memory, as demonstrated on line 84, and use the type. (If you use an emitted type in memory, you'll have to use reflection to invoke operations on the type. Writing the type to file will allow you to use Intellisense and strongly typed elements of the assembly.)

Finally, there's a simple ploy you can use to learn to write MSIL. It's the same trick you can use to learn to write C#: Read and write a lot of it. If you want to write a specific emitter, write a solution in C# and use the ildasm.exe utility to see how .NET emitted the IL.

  • + Share This
  • 🔖 Save To Your Account