
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
652CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
//Create the default ctor. helloWorldClass.DefineDefaultConstructor(MethodAttributes.Public);
//Now create the GetMsg() method.
MethodBuilder getMsgMethod = helloWorldClass.DefineMethod("GetMsg", MethodAttributes.Public, typeof(string), null);
ILGenerator methodIL = getMsgMethod.GetILGenerator(); methodIL.Emit(OpCodes.Ldarg_0); methodIL.Emit(OpCodes.Ldfld, msgField); methodIL.Emit(OpCodes.Ret);
//Create the SayHello method.
MethodBuilder sayHiMethod = helloWorldClass.DefineMethod("SayHello", MethodAttributes.Public, null, null);
methodIL = sayHiMethod.GetILGenerator(); methodIL.EmitWriteLine("Hello from the HelloWorld class!"); methodIL.Emit(OpCodes.Ret);
//"Bake" the class HelloWorld.
//(Baking is the formal term for emitting the type) helloWorldClass.CreateType();
//(Optionally) save the assembly to file. assembly.Save("MyAssembly.dll");
}
Emitting the Assembly and Module Set
The method body begins by establishing the minimal set of characteristics about your assembly, using the AssemblyName and Version types (defined in the System.Reflection namespace). Next, you obtain an AssemblyBuilder type via the instance-level AppDomain.DefineDynamicAssembly() method (recall the caller will pass in an AppDomain reference into the CreateMyAsm() method):
//Establish general assembly characteristics
//and gain access to the AssemblyBuilder type. public static void CreateMyAsm(AppDomain curAppDomain)
{
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);
...
}
As you can see, when calling AppDomain.DefineDynamicAssembly(), you must specify the access mode of the assembly you wish to define, which can be any of the values shown in Table 19-10.

CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
653 |
Table 19-10. Values of the AssemblyBuilderAccess Enumeration
Value |
Meaning in Life |
ReflectionOnly |
Represents that a dynamic assembly that can only be reflected over |
Run |
Represents that a dynamic assembly can be executed in memory but not saved |
|
to disk |
RunAndSave |
Represents that a dynamic assembly can be executed in memory and saved |
|
to disk |
Save |
Represents that a dynamic assembly can be saved to disk but not executed in |
|
memory |
|
|
The next task is to define the module set for your new assembly. Given that the assembly is a single file unit, you need to define only a single module. If you were to build a multifile assembly using the DefineDynamicModule() method, you would specify an optional second parameter that represents the name of a given module (e.g., myMod.dotnetmodule). However, when creating a singlefile assembly, the name of the module will be identical to the name of the assembly itself. In any case, once the DefineDynamicModule() method has returned, you are provided with a reference to a valid ModuleBuilder type:
// The single-file assembly.
ModuleBuilder module = assembly.DefineDynamicModule("MyAssembly", "MyAssembly.dll");
The Role of the ModuleBuilder Type
ModuleBuilder is the key type used during the development of dynamic assemblies. As you would expect, ModuleBuilder supports a number of members that allow you to define the set of types contained within a given module (classes, interfaces, structures, etc.) as well as the set of embedded resources (string tables, images, etc.) contained within. Table 19-11 describes a few of the creationcentric methods. (Do note that each method will return to you a related type that represents the type you wish to construct.)
Table 19-11. Select Members of the ModuleBuilder Type
Method |
Meaning in Life |
DefineEnum() |
Used to emit a .NET enum definition |
DefineResource() |
Defines a managed embedded resource to be stored in this module |
DefineType() |
Constructs a TypeBuilder, which allows you to define value types, interfaces, |
|
and class types (including delegates) |
|
|
The key member of the ModuleBuilder class to be aware of is DefineType(). In addition to specifying the name of the type (via a simple string), you will also make use of the System.Reflection. TypeAttributes enum to describe the format of the type itself. Table 19-12 lists some (but not all) of the key members of the TypeAttributes enumeration.



656 CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
//Create the SayHello method.
MethodBuilder sayHiMethod = helloWorldClass.DefineMethod("SayHello", MethodAttributes.Public, null, null);
methodIL = sayHiMethod.GetILGenerator();
//Write a line to the Console. methodIL.EmitWriteLine("Hello there!"); methodIL.Emit(OpCodes.Ret);
Here you have established a public method (MethodAttributes.Public) that takes no parameters and returns nothing (marked by the null entries contained in the DefineMethod() call). Also note the EmitWriteLine() call. This helper member of the ILGenerator class automatically writes a line to the standard output with minimal fuss and bother.
Using the Dynamically Generated Assembly
Now that you have the logic in place to create and save your assembly, all that’s needed is a class to trigger the logic. To come full circle, assume your current project defines a second class named AsmReader. The logic in Main() obtains the current AppDomain via the Thread.GetDoMain() method that will be used to host the assembly you will dynamically create. Once you have a reference, you are able to call the CreateMyAsm() method.
To make things a bit more interesting, once the call to CreateMyAsm() returns, you will exercise some late binding (see Chapter 16) to load your newly created assembly into memory and interact with the members of the HelloWorld class. Update your Main() method as follows:
static void Main(string[] args)
{
Console.WriteLine("***** The Amazing Dynamic Assembly Builder App *****");
//Get the application domain for the current thread.
AppDomain curAppDomain = Thread.GetDomain();
//Create the dynamic assembly using our helper f(x).
CreateMyAsm(curAppDomain);
Console.WriteLine("-> Finished creating MyAssembly.dll.");
//Now load the new assembly from file.
Console.WriteLine("-> Loading MyAssembly.dll from file."); Assembly a = Assembly.Load("MyAssembly");
//Get the HelloWorld type.
Type hello = a.GetType("MyAssembly.HelloWorld");
//Create HelloWorld object and call the correct ctor.
Console.Write("-> Enter message to pass HelloWorld class: "); string msg = Console.ReadLine();
object[] ctorArgs = new object[1]; ctorArgs[0] = msg;
object obj = Activator.CreateInstance(hello, ctorArgs);
//Call SayHello and show returned string.
Console.WriteLine("-> Calling SayHello() via late binding."); MethodInfo mi = hello.GetMethod("SayHello");
mi.Invoke(obj, null);

CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
657 |
// Invoke method.
mi = hello.GetMethod("GetMsg"); Console.WriteLine(mi.Invoke(obj, null));
}
In effect, you have just created a .NET assembly that is able to create and execute .NET assemblies at runtime! That wraps up our examination of CIL and the role of dynamic assemblies. I hope this chapter has deepened your understanding of the .NET type system and the syntax and semantics of CIL.
■Note Be sure to load your dynamically created assembly into ildasm.exe to connect the dots between raw CIL code and the functionality within the System.Reflection.Emit namespace.
■Source Code The DynamicAsmBuilder project is included under the Chapter 19 subdirectory.
Summary
This chapter provided an overview of the syntax and semantics of CIL. Unlike higher-level managed languages such as C#, CIL does not simply define a set of keywords, but provides directives (used to define the structure of an assembly and its types), attributes (which further qualify a given directive), and opcodes (which are used to implement type members). You were introduced to the CIL compiler (ilasm.exe) and learned how to alter the contents of a .NET assembly with new CIL code and also the basic process of building a .NET assembly using raw CIL.
The latter half of this chapter introduced you to the System.Reflection.Emit namespace. Using these types, you are able to emit a .NET assembly on the fly to memory. As well, if you so choose, you may persist this in-memory image to a physical file. Recall that many types of System. Reflection.Emit will automatically generate the correct CIL directives and attributes using friendly types such as ConstructorBuilder, TypeBuilder, and so forth. The ILGenerator type can be used to inject the necessary CIL opcodes into a given member. While we do have a number of helper types that attempt to make the process of programming with the CIL opcode set more palatable, you must have an understanding of CIL when programming with dynamic assemblies.


P A R T 5
Introducing the .NET Base
Class Libraries

