Emitting IL Code at Runtime

You have more than likely heard that Visual Basic .NET, and in fact all .NET code, is emitted to an Intermediate Language (IL) form when you compile an application. The IL code is actually just-in-time compiled (JITted) before it is actually executed. This follows the Java model of byte code. The foundation for doing this is probably similar to the reasons that Sun implemented Java that way: someone envisions porting .NET to non-Windows machines. It makes sense and is a good idea.

Imagine, some vendor will port VB .NET to the Mac, Linux, and UNIX. Now smart VB .NET programmers everywhere can work on software for any company, anywhere . If you want to see some IL code, run the ildasm.exe (IL disassembler) utility in the framework bin directory and explore one of the .NET assemblies created in this chapter (see Figure 4.2). If you are familiar with assembler, you will detect a similarity between IL and assembly code for Intel-based processors.

Figure 4.2. IL disassembler showing the fragment of the IL code for the SimpleBinder class created earlier in the chapter.

graphics/04fig02.gif

TIP

You can use Windows Explorer to search for ildasm.exe if you aren't sure where your .NET framework code is located.

.NET supports and actually facilitates emitting IL code at runtime. This capability is implemented in the System.Reflection.Emit namespace as a collaboration of classes ending with the suffix Builder . A builder is a class that emits IL code; examples include AssemblyBuilder , ConstructorBuilder , CustomAttributeBuilder , EnumBuilder , EventBuilder , FieldBuilder , ILGenerator , LocalBuilder , MethodBuilder , ModuleBuilder , ParameterBuilder , PropertyBuilder , and TypeBuilder . As you can detect by the names , there is a builder for most of the major elements of code. There are also structures in System.Reflection.Emit representing tokens like events, fields, methods , parameters, properties, and the like.

The Builder classes, tokens, and ILGenerator are designed to facilitate emitting IL code at runtime. You might ask, "Why would I want to do this?" I can't tell you all the reasons you might want to emit IL; I don't think most of them have been invented yet. I can tell you that Microsoft has offered some likely scenarios for emitting IL, including creating dynamic modules, running script in a browser, running script in an ASP.NET Web page, and compiling regular expressions. (See the help information on the System.Text.RegularExpressions namespace in Visual Studio .NET for more information on regular expressions.)

Some of the tasks you can perform with the Reflection.Emit classes include defining and saving an assembly at runtime; defining and saving modules at runtime; defining, creating instances of, and invoking members of types at runtime; and defining symbolic debugging formation for debuggers and code profilers.

TIP

A good resource on regular expressions is the e-book Regular Expressions in .NET by Dan Appleman [2002]. This 75-page book is available in PDF format for download.

Emitting is easily a subject that will ultimately need its own book. Unfortunately we have a limited amount of space; instead of trying to describe all the types in Reflection.Emit I will pick one example. Regular expressions provide a language for finding and replacing text patterns. Compiled regular expressions can be converted to machine code instead of regular expression instructions, providing an optimization. Regular expressions are useful, and emitting compiled regular expressions sounds like fun (at ”let me look ”2:29 in the morning), so we'll create an example that emits compiled regular expressions at runtime.

A Quick Review of Regular Expressions

The Regex class is one of the mainstays of the System.Text.RegularExpressions namespace. Regex has shared and instance methods that escapes and unescapes special metacharacters; match an input string against a pattern string; split strings; and replace patterns found in an input string. (You can look up individual Regex class members in the Visual Studio .NET help documentation.)

A common use for a regular expression is to provide a pattern and an input string and let the Regex class indicate whether the input string matches the pattern. Regular expressions can be very simple or quite complex. For example, you can check whether a string of characters are all digits with a very simple regular expression.

 ^\d+$ 

An example of a more complex regular expression changes a date in the format month/day/year to the format day-month-year .

 \b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b 

The regular expression ^\d+$ is read, "Start at the beginning of the string and match one or more digits until the end of the string." This pattern will match contiguous digits. The second regular expression stores the first one or two digits in <month> , the second one or two digits in <day> , and the final two or four digits in <year> .

NOTE

Designate your company's toolsmith as the person who specializes in regular expressions. When someone needs to perform some parsing, instead of writing code, have the toolsmith put together a single regular expression.

Clearly regular expressions can be terse. (That's one reason a book like Regular Expressions in .NET by Dan Appleman is helpful.) But consider the cost in lines of code that would perform an equivalent parsing operation. The regular expression parser packs a powerful punch in a single line of terse characters.

Compiled Regular Expressions

If you use a regular expression one time, you can invoke one of the shared methods. For example, Regex.IsMatch("12345" , "^\d+$") will return True , indicating that the input string "12345" is in fact a contiguous string of digits. If you are going to invoke an operation on the same regular expression many times, explicitly create an instance of the Regex class, and call the instance method.

 Imports System.Text.RegularExpressions Dim Expression As Regex = New Regex("^\d+$") Regex.IsMatch("12345") 

The Imports statement is placed at the top of the module. The Regex object is declared and created as a field in the class, and the Regex.IsMatch method is invoked multiple times, perhaps passing in the input string as a variable. (The code fragment shows a constant.)

NOTE

I performed a couple of simple tests evaluating a million random string digits and got about a 50 percent better performance result with compiled regular expressions, but this is not scientific. I would consider this an optimizing step. For example, if you have a search engine scanning millions of Web pages for HTML content, you might want to experiment with compiled regular expressions and perform your own analysis.

Another refinement you can make is to compile the regular expression. As defined in the preceding code fragment, the regular input string is parsed and compared using an interpreter. If you create an instance of the regular expression object with the RegexOptions.Compiled argument, the regular expression is converted to IL code and JITted, purportedly resulting in better performance. To compile the regular expression introduced in the prior fragment we need to make the following revision to the constructor invocation.

 Dim Expression As Regex = New Regex("^\d+$", RegexOptions.Compiled) 

Emitting a Type Dynamically

Emitting code dynamically is easier than it could be but is not for the weak of heart. Experimenting with emitted code, it took me about two minutes to create a simple type with a couple of fields, properties, a constructor, and validation methods for the properties. It took about ten hours to create approximately the same class and assembly emitting code with Reflection, representing about two orders of magnitude difference in time.

The benefit of emitting code is that you will be able to truly create dynamic, postdeployment behaviors. As good as the classes in the Emit namespace are, there is still a tremendous amount of work involved. In a nutshell , you have to write code to do all the things you would do manually in the Visual Studio .NET IDE. For example, you have to create an assembly; add modules to that assembly; define types; and add fields, properties, methods, events, constructors, and code to all those elements. And that will get you a basic type. The good news is that after you get the IL code right, your emitter should work every time.

To demonstrate some of what is involved I borrowed from the ReflectionEmit sample that ships with Visual Studio .NET. (You can usually find this code in the FrameworkSDK\Samples\Technologies\Reflection\ ReflectionEmit\vb\EmitAssembly.vb sample file. The best way to find this file is to search in Windows Explorer.) The CreateCallee method, taken from the EmitAssembly.vb sample code, is provided in Listing 4.16.

Listing 4.16 Emitting a Dynamic Type to Memory by Using Reflection
 1:  ' Create the callee transient dynamic assembly. 2:  Private Shared Function CreateCallee(appDomain As AppDomain, _ 3:    access As AssemblyBuilderAccess) As Type 4: 5:    ' Create a simple name for the callee assembly. 6:    Dim assemblyName As New AssemblyName() 7:    assemblyName.Name = "EmittedAssembly" 8: 9:    ' Create the callee dynamic assembly. 10:   Dim [assembly] As AssemblyBuilder = _ 11:     appDomain.DefineDynamicAssembly(assemblyName, access) 12: 13:   ' Create a dynamic module named "CalleeModule" in the callee assembly. 14:   Dim [module] As ModuleBuilder 15:   If access = AssemblyBuilderAccess.Run Then 16:     [module] = [assembly].DefineDynamicModule("EmittedModule") 17:   Else 18:     [module] = [assembly].DefineDynamicModule("EmittedModule", _ 19:     "EmittedModule.mod") 20:   End If 21: 22:   ' Define a public class named "HelloWorld" in the assembly. 23:   Dim helloWorldClass As TypeBuilder = [module].DefineType( _ 24:     "HelloWorld", TypeAttributes.Public) 25: 26:   ' Define a private String field named "Greeting" in the type. 27:   Dim greetingField As FieldBuilder = helloWorldClass.DefineField( _ 28:     "Greeting", GetType(String), FieldAttributes.Private) 29: 30:   ' Create the constructor. 31:   Dim constructorArgs As Type() = {GetType(String)} 32:   Dim constructor As ConstructorBuilder = _ 33:     helloWorldClass.DefineConstructor( _ 34:     MethodAttributes.Public, CallingConventions.Standard, _ 35:     constructorArgs) 36: 37:   ' Generate IL for the method. The constructor calls its superclass 38:   ' constructor. The constructor stores its argument in the private field. 39:   Dim constructorIL As ILGenerator = constructor.GetILGenerator() 40:   constructorIL.Emit(OpCodes.Ldarg_0) 41:   Dim superConstructor As ConstructorInfo = _ 42:     GetType(Object).GetConstructor(Type.EmptyTypes) 43:   constructorIL.Emit(OpCodes.Call, superConstructor) 44:   constructorIL.Emit(OpCodes.Ldarg_0) 45:   constructorIL.Emit(OpCodes.Ldarg_1) 46:   constructorIL.Emit(OpCodes.Stfld, greetingField) 47:   constructorIL.Emit(OpCodes.Ret) 48: 49:   ' Create the GetGreeting method. 50:   Dim getGreetingMethod As MethodBuilder = _ 51:     helloWorldClass.DefineMethod("GetGreeting", _ 52:     MethodAttributes.Public, GetType(String), Nothing) 53: 54:   ' Generate IL for GetGreeting. 55:   Dim methodIL As ILGenerator = _ 56:     getGreetingMethod.GetILGenerator() 57:   methodIL.Emit(OpCodes.Ldarg_0) 58:   methodIL.Emit(OpCodes.Ldfld, greetingField) 59:   methodIL.Emit(OpCodes.Ret) 60: 61:   ' Bake the class HelloWorld. 62:   Return helloWorldClass.CreateType() 63: End Function 'CreateCallee 

I certainly don't want to discourage anyone from experimenting with Reflection.Emit because it is really cool and some good things will come out of this technology. However, keep in mind that all the code above produces approximately the following code in an in-memory assembly and module.

 Public Class HelloWorld   Private Greeting As String   Public Sub New(S As String)     MyBase.New()     Greeeting = S   End Sub   Public Function GetGreeting() As String     Return Greeting   End Function End Class 

As you can see, Listing 4.16 does not emit a lot of code. Fortunately there are bound to be tool builders that will extend Reflection.Emit and create tools that will emit more code with simpler constructs. Let's take a moment to understand how the code is emitted.

Lines 6 and 7 create an assembly name, which is used to create an assembly (think ".NET application") on lines 10 and 11. DefineDynamicAssembly creates an in-memory assembly. (This code emits an assembly to memory, not to disk.) Lines 15 through 20 are used to determine the kind of module to create. (Modules are units of code.) If AssemblyBuilderAccess.Save is used to create the dynamic assembly, the AssemblyBuilder.Save method can be used to write the assembly to a file. After line 20 executes, a module as been emitted. We can add code to modules.

Notice the rampant use of name Builder classes. These are the classes we spoke of earlier in the chapter, from the System.Reflection.Emit namespace. The Builder classes make our job quite a bit easier, although not easy. Lines 23 and 24 create a TypeBuilder object, which is used to emit classes and structures.

Lines 27 and 28 add the Greeting field to the type (using a FieldBuilder) by providing a name, field type, and access specifier . (These are things you would actually do if you were writing this code from scratch.)

Lines 31 through 47 emit a constructor that calls the superclass constructor and assigns the constructor parameter to the Greeting field. OpCodes are defined in the help documentation. OpCodes are pure IL; they look like pseudo-assembly code when emitted to a file. (You can use ildasm.exe to view IL code in a compiled assembly.) For example, line 43 actually invokes the parent class constructor. Line 43 equates to MyBase.New() .

Lines 50 through 59 emit the GetGreeting function. MethodBuilders generate subroutines and functions. You specify a name, access specifier, return type, and argument types. GetGreeting is defined to return a string and take no arguments, as codified in line 52. Consistently throughout Listing 4.16, the Builder objects are used to request an ILGenerator object. The ILGenerator class is actually emitting the code (see lines 57 through 59). Finally we call TypeBuilder.CreateType to create the HelloWorld class on line 62.

It takes quite a bit of work to rough-frame a dynamic assembly. You can use two tricks to help you get started: (1) think of the emitted code the same way you would if you were typing in the code, and (2) actually write a demo program and create the code manually. Use ildasm.exe to view the IL generated when you type the code and emulate that code. There are differences, and you will want to make some aspects of your emitter generic, allowing the emitted code to vary. If you want an emitter to emit the same code, you might as well write the code one time.

Let's get back to where we left off in the preceding section: emitting compiled regular expressions.

Emitting a Compiled Regular Expression Dynamically

Someone has already made at least one utility based on Reflection. (There will probably be dozens of good ones by the time you read this.) The Regex class has a CompileToAssembly method that will generate a dynamic assembly with very little effort.

From our previous discussion we can discern the value of a dynamically generated assembly containing compiled regular expressions. Recall that compiled regular expressions can offer performance benefits over uncompiled regular expressions. Suppose you have some useful regular expressions that your customers are using heavily in your deployed applications ”or better, you create a tool that allows you to experiment with regular expressions and automatically bundle and ship them to customers when you get them right. You could allow the customer to dynamically compile the most heavily used expressions on demand. Listing 4.17 demonstrates how you can emit a compiled regular expression on demand. (The complete example program is defined in ReflectionEmit.sln .)

Listing 4.17 Emitting a Compiled Regular Expression into an Assembly at Runtime
 1:  Imports System.Reflection 2:  Imports System.Text.RegularExpressions 3: 4:  Private Sub EmitRegularExpressionAssembly() 5:    Dim Expression As String = InputBox("Expression") 6:    Dim Title As String = InputBox("Expression Name") 7: 8:    Dim CompilationInfo() As RegexCompilationInfo = _ 9:      {New RegexCompilationInfo(Expression, RegexOptions.Compiled, _ 10:     Title, "CompiledRegularExpressions", True)} 11: 12:   Dim Name As AssemblyName = New AssemblyName() 13:   Name.Name = "Regex" 14:   Regex.CompileToAssembly(CompilationInfo, Name) 15: End Sub 

This code emits a relatively complex assembly containing a class that inherits from the RegexRunner , RegexRunnerFactory , and Regex classes. (A lot of work someone did for us.) The code we have to write is relatively straightforward.

Lines 1 and 2 import the namespaces we need to emit the compiled regular expression assembly. Lines 5 and 6 ask the user for a regular expression and a name for that expression, representing a user interface. Lines 8, 9, and 10 are key. The statement beginning on line 8 and ending on line 10 creates an instance of an array of RegexCompilationInfo objects. We can add multiple expressions by defining multiple RegexCompilationInfo objects. Our array has only one. The regular expression is passed to RegexCompilationInfo first, followed by RegexOptions . RegexOptions.Compiled will cause our regular expression to be compiled when it is emitted. The third argument is the expression name, the fourth is the namespace, and the last argument indicates whether the access modifier is public for the regular expression class emitted.

Lines 12 and 13 create an assembly name for our regular expression assembly, and line 14 emits the assembly. When finished there is a DLL assembly named regex.dll that you can reference and invoke regular expressions on or that you can load dynamically using Reflection and use in the same application that emitted the regular expression assembly. (See ReflectionEmit.sln for a working demo program.)



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net