Blog Archives
Reflection (part 6, professional), Emit
Reflection can discover information about objects at runtime and execute against those objects. The namespace System.Reflection.Emit also allows you to build assemblies and create types at runtime. Only a few programmers will come across Emit. You have to be a kind of Rambo sewing yourself while fighting … and definitely not Alice in Wonderland.
Emit generates IL code in memory. The source code for such is quite complex and difficult. We are nearly back to Assembler style. And if you remember well, crashes and difficult debugging were the order of the day. There are tools like the EmitHelper class out there to make Emit easier. I am going to show the fundamentals today, this does not cover tools.
Today’s word list:
Domain
Application domains aid security, separating applications from each other and each other’s data. A single process can run several application domains, with the same level of isolation that would exist in separate processes. Running multiple applications within a single process increases server scalability.
Faults in one application domain cannot affect other code running in another application domain.
Module
A module is a portable executable file, such as type.dll or application.exe, consisting of one or more classes and interfaces. There may be multiple namespaces contained in a single module, and a namespace may span multiple modules.
One or more modules deployed as a unit compose an assembly.
The hierarchy is: domain => assemblies => modules => classes => functions
Ildasm.exe
A disassembler for MSIL code.
The first step is to create an executable file from:
using System; using System.Reflection; namespace HelloWorld { public class Program { static void Main(string[] args) { } // public string Test() { return string.Format("DateTime is {0:dd MMM yyyy HH:mm:ss}", DateTime.Now); } // public string Test2() { return DateTime.UtcNow.ToString(); } // public string Test3() { return Assembly.GetExecutingAssembly().ToString(); } // public void Test4() { Console.WriteLine("hello world !"); } // } // class } // namespace
On the windows taskbar, click Start, click All Programs, click Visual Studio, click Visual Studio Tools, and then click Visual Studio Command Prompt.
-or-
If you have the Windows SDK installed on your computer: On the taskbar, click Start, click All Programs, click the folder for the Windows SDK, and then click Command Prompt (or CMD Shell).
Change to the right folder and enter “ildasm HelloWorld.exe /output:HelloWorld.il”.
In your folder you should see something like this:
Open the HelloWorld.il file in a text editor and delve into the following sections:
method Test()
.method public hidebysig instance string Test() cil managed { // Code size 21 (0x15) .maxstack 8 IL_0000: ldstr "DateTime is {0:dd MMM yyyy HH:mm:ss}" IL_0005: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now() IL_000a: box [mscorlib]System.DateTime IL_000f: call string [mscorlib]System.String::Format(string, object) IL_0014: ret } // end of method Program::Test
method Test3()
.method public hidebysig instance string Test2() cil managed { // Code size 20 (0x14) .maxstack 1 .locals init ([0] valuetype [mscorlib]System.DateTime CS$0$0000) IL_0000: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_UtcNow() IL_0005: stloc.0 IL_0006: ldloca.s CS$0$0000 IL_0008: constrained. [mscorlib]System.DateTime IL_000e: callvirt instance string [mscorlib]System.Object::ToString() IL_0013: ret } // end of method Program::Test2
method Test3()
.method public hidebysig instance string Test3() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: call class [mscorlib]System.Reflection.Assembly [mscorlib]System.Reflection.Assembly::GetExecutingAssembly() IL_0005: callvirt instance string [mscorlib]System.Object::ToString() IL_000a: ret } // end of method Program::Test3
method Test4()
.method public hidebysig instance void Test4() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "hello world !" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method Program::Test4
These code sections give you a rough idea of what we are going to code in runtime.
Start a new console project. You have to edit the file “AssemblyInfo.cs”. Add the assembly attribute AllowPartiallyTrustedCallersAttribute. The program won’t run otherwise. We are on a low coding level and Microsoft obviously tries to protect code especially on that level.
AssemblyInfo.cs
using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("oink")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("blablabla")] [assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: AllowPartiallyTrustedCallersAttribute] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("0bf893aa-f3da-49ef-a2dc-a63d8ffc9ead")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
The Print() method is not big, nevertheless you find something unusual here. The keyword __arglist is not official. It has been implemented by Microsoft, but is not documented at all. It is also not well supported in C#. For instance you cannot use a foreach loop. __arglist takes any number of optional arguments like object[]. In our example it does make a big difference to use __arglist. We do not have to create an array and fill it in. This would be much more code in Emit. On the contrary the __arglist algorithm is quite short and comfortable. Follow the link for more __arglist documentation.
public static void Print(string xHeader, __arglist) { ArgIterator lIterator = new ArgIterator(__arglist); Console.WriteLine(xHeader); while (lIterator.GetRemainingCount() > 0) { TypedReference r = lIterator.GetNextArg(); object o = TypedReference.ToObject(r); Console.Write(o); } Console.WriteLine(); Console.WriteLine(); } //
EmitCode() is the actual code generation part. Let’s call the remaining source code “administration” to simplify the situation.
There are two ways to get the MethodInfo for a property. One is used for DateTime.Now, the other one is used for DateTime.UtcNow .
They can be used equally. I used both ways for demonstration purposes only.
I tried to keep the example simple. You don’t have to study the MSIL to understand the code. Some information about MSIL OpCodes can be found here.
static void EmitCode(ILGenerator xILGenerator) { MethodInfo lMethodInfo_Print = typeof(EmitDemo).GetMethod("Print"); MethodInfo lDateTime_Now = typeof(DateTime).GetProperty("Now").GetGetMethod(); MethodInfo lFormat = typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }); xILGenerator.Emit(OpCodes.Ldstr, "DateTime is {0:dd MMM yyyy HH:mm:ss}"); xILGenerator.Emit(OpCodes.Call, lDateTime_Now); xILGenerator.Emit(OpCodes.Box, typeof(DateTime)); xILGenerator.Emit(OpCodes.Call, lFormat); xILGenerator.EmitCall(OpCodes.Call, lMethodInfo_Print, new Type[] { }); xILGenerator.Emit(OpCodes.Ldstr, "This equals UTC: "); xILGenerator.Emit(OpCodes.Call, typeof(DateTime).GetMethod("get_UtcNow")); // compare this with lDateTime_Now (== same, just another demo approach) xILGenerator.EmitCall(OpCodes.Call, lMethodInfo_Print, new Type[] { typeof(DateTime) }); xILGenerator.Emit(OpCodes.Ldstr, "The assembly is: "); xILGenerator.Emit(OpCodes.Call, typeof(Assembly).GetMethod("GetExecutingAssembly")); xILGenerator.EmitCall(OpCodes.Call, lMethodInfo_Print, new Type[] { typeof(Assembly) }); xILGenerator.Emit(OpCodes.Ldstr, "Console.WriteLine() is old school."); xILGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) })); xILGenerator.EmitWriteLine("EmitWriteLine() is for lazy programmers."); xILGenerator.Emit(OpCodes.Ret); } //
Test1_ViaFullAssembly() shows you how to properly initialize and use an assembly. Remember the hierarchy from above: domain => assemblies => modules => classes => functions
The method is build like a chain. Each statement uses the result of the previous one.
public static void Test1_ViaFullAssembly() { AssemblyName lAssemblyName = new AssemblyName("MyAssembly"); AssemblyBuilder lAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(lAssemblyName, AssemblyBuilderAccess.Run); ModuleBuilder lModuleBuilder = lAssemblyBuilder.DefineDynamicModule("MyModule"); TypeBuilder lTypeBuilder = lModuleBuilder.DefineType("MyType"); MethodBuilder lMethodBuilder = lTypeBuilder.DefineMethod("DoSomething", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), Type.EmptyTypes); EmitCode(lMethodBuilder.GetILGenerator()); Type lType = lTypeBuilder.CreateType(); lType.GetMethod("DoSomething").Invoke(null, null); Console.ReadLine(); } //
example output:
DateTime is 15 Jan 2014 23:17:30This equals UTC:
15/01/2014 23:17:30The assembly is:
MyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=nullConsole.WriteLine() is old school.
EmitWriteLine() is for lazy programmers.
You can avoid building a full assembly. DynamicMethod simplifies the dynamic creation of methods. Of course you won’t have any class structure. The method is super-public, it reminds me of VBA modules.
public static void ViaDynamicMethod() { DynamicMethod lDynamicMethod = new DynamicMethod("DoSomething", typeof(void), Type.EmptyTypes, typeof(object)); EmitCode(lDynamicMethod.GetILGenerator()); lDynamicMethod.Invoke(null, null); Console.ReadLine(); } //
example output:
DateTime is 15 Jan 2014 23:17:58This equals UTC:
15/01/2014 23:17:58The assembly is:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089Console.WriteLine() is old school.
EmitWriteLine() is for lazy programmers.