
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf


644CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
//Reference mscorlib.dll and
//System.Windows.Forms.dll
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
// Define the single-file assembly.
.assembly CILCars
{
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module CILCars.dll
As mentioned, this assembly will contain two class types. The first type, CILCar, defines two points of field data and a custom constructor. The second type, CarInfoHelper, defines a single static method named DisplayCarInfo(), which takes CILCar as a parameter and returns void. Both types are in the CILCars namespace. In terms of CIL, CILCar can be implemented as follows:
// Implementation of CILCars.CILCar type.
.namespace CILCars
{
.class public auto ansi beforefieldinit CILCar extends [mscorlib]System.Object
{
//The field data of the CILCar.
.field public string petName
.field public int32 currSpeed
//The custom constructor simply allows the caller
//to assign the field data.
.method public hidebysig specialname rtspecialname instance void .ctor(int32 c, string p) cil managed
{
.maxstack 8
//Load first arg onto the stack and call base class ctor. ldarg.0 // "this" object, not the int32!
call instance void [mscorlib]System.Object::.ctor()
//Now load first and second args onto the stack.
ldarg.0 // "this" object ldarg.1 // int32 arg
//Store topmost stack (int 32) member in currSpeed field. stfld int32 CILCars.CILCar::currSpeed
//Load string arg and store in petName field.
ldarg.0 // "this" object ldarg.2 // string arg

CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
645 |
stfld string CILCars.CILCar::petName ret
}
}
}
Keeping in mind that the real first argument for any nonstatic member is the current object reference, the first block of CIL simply loads the object reference and calls the base class constructor. Next, you push the incoming constructor arguments onto the stack and store them into the type’s field data using the stfld (store in field) opcode.
Now let’s implement the second type in this namespace: CILCarInfo. The meat of the type is found within the static Display() method. In a nutshell, the role of this method is to take the incoming CILCar parameter, extract the values of its field data, and display it in a Windows Forms message box. Here is the complete implementation of CILCarInfo, with analysis to follow:
.class public auto ansi beforefieldinit CILCarInfo extends [mscorlib]System.Object
{
.method public hidebysig static void Display(class CILCars.CILCar c) cil managed
{
.maxstack 8
//We need a local string variable.
.locals init ([0] string caption)
//Load string and the incoming CILCar onto the stack. ldstr "{0}'s speed is:"
ldarg.0
//Now place the value of the CILCar's petName on the
//stack and call the static String.Format() method. ldfld string CILCars.CILCar::petName
call string [mscorlib]System.String::Format(string, object) stloc.0
//Now load the value of the currSpeed field and get its string
//representation (note call to ToString() ).
ldarg.0
ldflda int32 CILCars.CILCar::currSpeed
call instance string [mscorlib]System.Int32::ToString() ldloc.0
// Now call the MessageBox.Show() method with loaded values. call valuetype [System.Windows.Forms]
System.Windows.Forms.DialogResult
[System.Windows.Forms] System.Windows.Forms.MessageBox::Show(string, string)
pop ret
}
}
Although the amount of CIL code is a bit more than you see in the implementation of CILCar, things are still rather straightforward. First, given that you are defining a static method, you don’t have to be concerned with the hidden object reference (thus, the ldarg.0 opcode really does load the incoming CILCar argument).

646 CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
The method begins by loading a string ("{0}'s speed is") onto the stack, followed by the CILCar argument. Once these two values are in place, you load the value of the petName field and call the static System.String.Format() method to substitute the curly bracket placeholder with the CILCar’s pet name.
The same general procedure takes place when processing the currSpeed field, but note that you use the ldflda opcode, which loads the argument address onto the stack. At this point, you call System.Int32.ToString() to transform the value at said address into a string type. Finally, once both strings have been formatted as necessary, you call the MessageBox.Show() method.
At this point, you are able to compile your new *.dll using ilasm.exe with the following command:
ilasm /dll CILCars.il
and verify the contained CIL using peverify.exe:
peverify CILCars.dll
Building CILCarClient.exe
Now you can build a simple *.exe assembly that will
•Make a CILCar type.
•Pass the type into the static CILCarInfo.Display() method.
Create a new file named CarClient.il and define external references to mscorlib.dll and CILCars.dll (don’t forget to place a copy of this .NET assembly in the client’s application directory!). Next, define a single type (Program) that manipulates the CILCars.dll assembly. Here’s the complete code:
//External assembly refs.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly extern CILCars
{
.ver 1:0:0:0
}
//Our executable assembly.
.assembly CarClient
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module CarClient.exe
// Implementation of Program type
.namespace CarClient
{
.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{
.method private hidebysig static void



CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
649 |
example, rather than directly specifying the necessary CIL directives and attributes to define a class type, you can simply make use of the TypeBuilder class. Likewise, if you wish to define a new instance-level constructor, you have no need to emit the specialname, rtspecialname, or .ctor tokens; rather, you can make use of the ConstructorBuilder. Table 19-8 documents the key members of the System.Reflection.Emit namespace.
Table 19-8. Select Members of the System.Reflection.Emit Namespace
Members |
Meaning in Life |
AssemblyBuilder |
Used to create an assembly (*.dll or *.exe) at runtime. *.exes must |
|
call the ModuleBuilder.SetEntryPoint() method to set the method |
|
that is the entry point to the module. If no entry point is specified, a |
|
*.dll will be generated. |
ModuleBuilder |
Used to define the set of modules within the current assembly. |
EnumBuilder |
Used to create a .NET enumeration type. |
TypeBuilder |
May be used to create classes, interfaces, structures, and delegates |
|
within a module at runtime. |
MethodBuilder |
Used to create type members (such as methods, local variables, |
LocalBuilder |
properties, constructors, and attributes) at runtime. |
PropertyBuilder |
|
FieldBuilder |
|
ConstructorBuilder |
|
CustomAttributeBuilder |
|
ParameterBuilder |
|
EventBuilder |
|
ILGenerator |
Emits CIL opcodes into a given type member. |
OpCodes |
Provides numerous fields that map to CIL opcodes. This type is used |
|
in conjunction with the various members of System.Reflection. |
|
Emit.ILGenerator. |
|
|
In general, the types of the System.Reflection.Emit namespace allow you to represent raw CIL tokens programmatically during the construction of your dynamic assembly. You will see many of these members in the example that follows; however, the ILGenerator type is worth checking out straightaway.
The Role of the System.Reflection.Emit.ILGenerator
As its name implies, the ILGenerator type’s role is to inject CIL opcodes into a given type member. However, you cannot directly create ILGenerator objects, as this type has no public constructors, rather you receive an ILGenerator type by calling specific methods of the builder-centric types (such as the MethodBuilder and ConstructorBuilder types), for example:
//Obtain an ILGenerator from a ConstructorBuilder
//object named "myCtorBuilder".
ConstructorBuilder myCtorBuilder =
new ConstructorBuilder(/* ...various args... */); ILGenerator myCILGen = myCtorBuilder.GetILGenerator();
Once you have an ILGenerator in your hands, you are then able to emit the raw CIL opcodes using any number of methods. Table 19-9 documents some (but not all) methods of ILGenerator.

650 CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
Table 19-9. Various Methods of ILGenerator
Method |
Meaning in Life |
BeginCatchBlock() |
Begins a catch block |
BeginExceptionBlock() |
Begins an exception block for a nonfiltered exception |
BeginFinallyBlock() |
Begins a finally block |
BeginScope() |
Begins a lexical scope |
DeclareLocal() |
Declares a local variable |
DefineLabel() |
Declares a new label |
Emit() |
Is overloaded numerous times to allow you to emit CIL opcodes |
EmitCall() |
Pushes a call or callvirt opcode into the CIL stream |
EmitWriteLine() |
Emits a call to Console.WriteLine() with different types of values |
EndExceptionBlock() |
Ends an exception block |
EndScope() |
Ends a lexical scope |
ThrowException() |
Emits an instruction to throw an exception |
UsingNamespace() |
Specifies the namespace to be used in evaluating locals and watches for |
|
the current active lexical scope |
|
|
The key method of ILGenerator is Emit(), which works in conjunction with the System. Reflection.Emit.OpCodes class type. As mentioned earlier in this chapter, this type exposes a good number of read-only fields that map to raw CIL opcodes. The full set of these members are all documented within online help, and you will see various examples in the pages that follow.
Emitting a Dynamic Assembly
To illustrate the process of defining a .NET assembly at runtime, let’s walk through the process of creating a single-file dynamic assembly named MyAssembly.dll. Within this module is a class named HelloWorld. The HelloWorld type supports a default constructor and a custom constructor that is used to assign the value of a private member variable (theMessage) of type string. In addition, HelloWorld supports a public instance method named SayHello(), which prints a greeting to the standard I/O stream, and another instance method named GetMsg(), which returns the internal private string. In effect, you are going to programmatically generate the following class type:
//This class will be created at runtime
//using System.Reflection.Emit.
public class HelloWorld
{
private string theMessage; HelloWorld() {}
HelloWorld(string s) {theMessage = s;}
public string GetMsg() {return theMessage;} public void SayHello()
{
System.Console.WriteLine("Hello from the HelloWorld class!");
}
}
Assume you have created a new Visual Studio 2008 Console Application project workspace named DynamicAsmBuilder and import the System.Reflection, System.Reflection.Emit, and

CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
651 |
System.Threading namespaces. Define a static method named CreateMyAsm(). This single method is in charge of the following:
•Defining the characteristics of the dynamic assembly (name, version, etc.)
•Implementing the HelloClass type
•Saving the in-memory assembly to a physical file
Also note that the CreateMyAsm() method takes as a single parameter a System.AppDomain type, which will be used to obtain access to the AssemblyBuilder type associated with the current application domain (see Chapter 17 for a discussion of .NET application domains). Here is the complete code, with analysis to follow:
// The caller sends in an AppDomain type.
public static void CreateMyAsm(AppDomain curAppDomain)
{
//Establish general assembly characteristics.
AssemblyName assemblyName = new AssemblyName(); assemblyName.Name = "MyAssembly"; assemblyName.Version = new Version("1.0.0.0");
//Create new assembly within the current AppDomain.
AssemblyBuilder assembly = curAppDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);
//Given that we are building a single-file
//assembly, the name of the module is the same as the assembly.
ModuleBuilder module = assembly.DefineDynamicModule("MyAssembly", "MyAssembly.dll");
//Define a public class named "HelloWorld".
TypeBuilder helloWorldClass = module.DefineType("MyAssembly.HelloWorld",
TypeAttributes.Public);
//Define a private String member variable named "theMessage".
FieldBuilder msgField =
helloWorldClass.DefineField("theMessage", Type.GetType("System.String"), FieldAttributes.Private);
//Create the custom ctor.
Type[] constructorArgs = new Type[1]; constructorArgs[0] = typeof(string); ConstructorBuilder constructor =
helloWorldClass.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard,
constructorArgs);
ILGenerator constructorIL = constructor.GetILGenerator(); constructorIL.Emit(OpCodes.Ldarg_0);
Type objectClass = typeof(object); ConstructorInfo superConstructor =
objectClass.GetConstructor(new Type[0]); constructorIL.Emit(OpCodes.Call, superConstructor); constructorIL.Emit(OpCodes.Ldarg_0); constructorIL.Emit(OpCodes.Ldarg_1); constructorIL.Emit(OpCodes.Stfld, msgField); constructorIL.Emit(OpCodes.Ret);