Browse DevX
Sign up for e-mail newsletters from DevX


Reflection Part II: Emit : Page 2

In our previous article, Reflection Part 1: Discovery and Execution , we introduced the System.Reflection namespace and its classes which allow developers to view assembly metadata, query for and discover types, and invoke code—all at run-time. In this article we will examine reflection emit—the ability to dynamically generate code at runtime.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Code Generation Process
Following the steps listed above, let's examine the exact operations that are necessary to build out an assembly. For the purposes of this very basic example, let's assume that you want to build a class called MathOps that has one public method (a function). This function will accept two input parameters as integers and return the sum of those two integers.

Step 1: Building the Assembly.
To expand slightly on the steps listed above, the actual process for step 1 looks like this:

  1. Create an AssemblyName. (This is used to uniquely identify and name the assembly.)
  2. Grab a reference to the current application domain. (The app domain will provide the actual method that returns an AssemblyBuilder object.)
  3. Create an AssemblyBuilder instance by calling AppDomain.DefineDynamicAssembly.
To start the assembly generation process you first need to create an AssemblyName instance that you'll use to identify your assembly.

' Create a name for the assembly. Dim name As New AssemblyName() name.Name = "MyAssembly"

Next, you need an instance of a System.AppDomain class. You can get this from the currently running (static) thread instance.

Dim ad As AppDomain ad = Thread.GetDomain()

With these two elements in place you can define an AssemblyBuilder class and create an instance of it using the AppDomain and AssemblyName that you previously created. The AssemblyBuilder class is the work-horse of reflection emit. It provides the primary mechanisms you need to create new assemblies from scratch. You also need to specify an AssemblyBuilderAccess enumeration value that will indicate whether you want the assembly written to disk, saved in memory, or both. In this example, you want to save the assembly in memory.

Dim ab As AssemblyBuilder ab = ad.DefineDynamicAssembly(name, _ AssemblyBuilderAccess.Run)

Step 2: Defining a Module.
In Step 2 you'll use the ModuleBuilder class to create a dynamic module inside of the assembly that you previously created. ModuleBuilder creates modules inside of an assembly. Calling the DefineDynamicModule method of the AssemblyBuilder object will return an instance of a ModuleBuilder. As with the assembly, you must give the module a name (although here, the name is just a string).

Dim mb As ModuleBuilder mb = ab.DefineDynamicModule("MyModule")

Step 3: Creating a Type.
Now that you have an assembly and a module, you can add your class into the assembly. To build a new type (in your case you're building a MyClass type), you use the TypeBuilder class. You'll use a method from the "parent" object to actually return an instance of the builder object.

Dim theClass As TypeBuilder theClass = mb.DefineType("MathOps", _ TypeAttributes.Public)

Note that you've specified the visibility of the type as public by using the TypeAttributes enumeration.

Step 4: Adding a Method.
With the class type created, you can now add your method to the class. Call the method ReturnSum, and create it as a public function.

Use the MethodBuilder class to specify methods for a specific type. You can create a MethodBuilder instance by calling DefineMethod on your previously created type object. DefineMethod expects four parameters: the method, any attributes the method might possess (such as public, private, etc.), parameters, and a return type. Parameters and return type can be void values in the case of a subroutine. In your case you're creating a function so you need to specify both the parameters and return type values.

To specify the return type, create a type object that holds a return type value (a System.Int32 value).

Dim retType As Type retType = GetType(System.Int32)

You'll use an array of type values to specify the parameters to the function. Both of the parameters are int32 values as well.

Dim parms As Type() parms(0) = GetType(System.Int32) parms(1) = GetType(System.Int32)

With these items in hand you can now call DefineMethod.

Dim mb As MethodBuilder = _ theClass.DefineMethod("ReturnSum", _ methodAttributes.Public, retType, parms)

Step 5: Generating Code.
Since you have, in essence, stubbed out the function in step 4, you now need to add the actual code body to the method. This is really the core of the code generation process with reflection emit.

Note that reflection emit classes do not generate source code. In other words, your efforts here won't create Visual Basic .NET or C# .NET code. Instead, your reflection emit classes will emit MSIL op codes.
It is important to note that reflection emit classes do not generate source code. In other words, your efforts here won't create Visual Basic .NET or C# .NET code. Instead, your reflection emit classes will emit MSIL op codes. MSIL (Microsoft Intermediate Language) is an intermediate code language that closely resembles assembler. MSIL is consumed by the .NET JIT compiler when it creates native binaries. Op codes are low-level, assembler-like operating instructions.

Consider the following implementation of ReturnSum:

Function ReturnSum(ByVal val1 As Integer, _ ByVal val2 As Integer) As Integer Return val1 + val2 End Function

If you want to emit this code, you would first need to figure out how to write this function using only MSIL op codes. Thankfully, there is a quick and easy way to do this. You can simply compile the code and examine the resulting assembly using the .NET Framework ILDASM.exe utility. Compiling the function above yields the following MSIL version:

.method public instance int32 ReturnSum(int32 val1, int32 val2) cil managed { // Code size 9 (0x9) .maxstack 2 .locals init ([0] int32 ReturnSum) IL_0000: nop IL_0001: ldarg.1 IL_0002: ldarg.2 IL_0003: add.ovf IL_0004: stloc.0 IL_0005: br.s IL_0007 IL_0007: ldloc.0 IL_0008: ret } // end of method MathOps::ReturnSum

To emit this code you will use the ILGenerator class. You can retrieve an instance of the ILGenerator class specific to your method by calling the MethodBuilder.GetILGenerator() method. In other words, the following code will obtain an ILGenerator instance for your ReturnSum method.

Dim gen As ILGenerator gen = mb.GetILGenerator()

Using the generator object you can inject op code instructions to the method.

gen.Emit(OpCodes.Ldarg_1) gen.Emit(OpCodes.Ldarg_2) gen.Emit(OpCodes.Add_Ovf) gen.Emit(OpCodes.Stloc_0) gen.Emit(OpCodes.Br_S) gen.Emit(OpCodes.Ldloc_0) gen.Emit(OpCodes.Ret)

At this point you've essentially finished creating the function, class, module, and assembly. To get a reference to this class you can make one call to CreateType like this:


Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date