JIT Coding in VB.NET

One not so well known feature of .NET platform is possibility to invoke compiler and practically create code and assembly from running instance of application. It is possible to do that in two ways. First one is a bit simpler and involves namespaces System.CodeDom and System.CodeDom.Compiler, second one is more efficient and utilizes namespace System.Reflection.Emit. Since there are very few examples about how to use System.CodeDom.Compiler I will start with it.

System.CodeDom.Compiler introductory example

As usual introductory example is about writing assembly which will write another assembly which will write to console string (incidentally "Hello World") received from first assembly. Very convenient. Here is code to do all that:

Imports System
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Reflection
Class test
Shared Sub Main()
Dim compunit As New CodeCompileUnit
Dim TheName As New CodeNamespace("TheName")
compunit.Namespaces.Add(TheName)
TheName.Imports.Add(
New CodeNamespaceImport("System"))
Dim Class1 As New CodeTypeDeclaration("Class1")
TheName.Types.Add(Class1)
Dim Say As New CodeMemberMethod
Say.Parameters.Add(
New CodeParameterDeclarationExpression(GetType(String), "s"))
Say.Statements.Add(
New CodeExpressionStatement(NewCodeSnippetExpression("System.Console.WriteLine(s)")))
Class1.TypeAttributes = TypeAttributes.Public
Class1.Members.Add(Say)
Say.Attributes = MemberAttributes.Public
Say.Name = "Say"
Dim compparams As New CompilerParameters(New String() {"mscorlib.dll"})
compparams.GenerateInMemory = 
True
'uncomment following if you like to write dll to disk
'compparams.OutputAssembly=" HelloWorld.dll";
Dim csharp As New Microsoft.CSharp.CSharpCodeProvider
Dim cscompiler As ICodeCompiler = csharp.CreateCompiler()
Dim compresult As CompilerResults = cscompiler.CompileAssemblyFromDom(compparams, compunit)
If compresult Is Nothing Or compresult.Errors.Count > 0 Then
Environment.Exit(1)
End If
Dim
 o As Object = compresult.CompiledAssembly.CreateInstance("TheName.Class1")
Dim
 test As Type = compresult.CompiledAssembly.GetType("TheName.Class1")
Dim m As MethodInfo = test.GetMethod("Say")
Dim arg(1) As Object
arg(0) = " Hello World!"
m.Invoke(o, arg)
End Sub 'Main
End Class 'test
 

More or less code is self-explanatory, you will declare namespace, imports, type and its name, method and its name and so on. Only unusual thing is:

Say.Statements.Add(New CodeExpressionStatement(NewCodeSnippetExpression("System.Console.WriteLine(s)")))

which allows you to insert expression using familiar syntax into the body of the method. Finally we will invoke compiler and using System.Reflection activate instance of that newly compiled assembly. I wanted to have that taking place in memory but if you like to examine more closely result of compilation uncomment:

//compparams.OutputAssembly=" HelloWorld.dll"

If you are not sure that example is sufficient don't worry there is another one later in the article and also you can try to find on the web example published by Clemens Vasters - (c) 2001 newtelligence AG. To try to explain everything would be quite difficult so I must ask readers to use here .NET Framework SDK Documentation to find out more about System.CodeDom.

Introductory example for System.Reflection.Emit was supplied by Microsoft and link to it could be found on "Microsoft .NET Framework SDK QuickStarts, Tutorials and Samples". 

Now when you went through it and got understanding of these two namespaces it is time to attend to some weaknesses of C# and IL in particular.

Mimicking C++ templates from VB.NET

Since IL doesn't support templates you can't have them supported in VB.NET or VB. I'm talking about C++ template here and this is elementary example:

template <class T>
T Sum(T a, T b)
{
return a + b;
}
void main()
{
cout << Sum(2, 3) << 
'\n' << Sum(1.1, 2.2) << '\n';
}

Only at runtime will be known type of variable and return type. Why are they important? During many years they were the base for generic programming and due to popularity of C++ most of generic software using them. Before I start long-lasting discussion let me mention interfaces. If that function should return bigger of two values we will use interface IComparable which is defined in System but there is no defined IAddible so I will also skip defining it. How is VB.NET handling the same problem? Usually using Object where casting to Object is certain because all types must inherit from it.

But object doesn't know how to add itself to another object. So you will have to roll out new class where operator + knows how to add two objects depending on their original type. Conventional solution of this problem I am leaving to readers and I'm jumping right onto unusual one. First comes the easy one involving System.CodeDom.Compiler.

Imports System
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.Reflection
Class T
Shared Sub Main()
Console.Write("Value = {0}" + ControlChars.Tab + "Type = {1}" + ControlChars.Lf, Mimic(1, 2).ToString(), Mimic(1, 2).GetType())
Console.Write("Value = {0}" + ControlChars.Tab + "Type = {1}" + ControlChars.Lf, Mimic(1.1, 3.14).ToString(), Mimic(1.1, 3.14).GetType())
Console.Write("Value = {0}" + ControlChars.Tab + "Type = {1}" + ControlChars.Lf, Mimic("Hello ", "World!").ToString(), Mimic("Hello ", "World!").GetType())Console.Read()
End Sub 'Main
Shared Function Mimic(ByVal a As ObjectByVal b As ObjectAs Object
If
 a.GetType() <> b.GetType() Then
Environment.Exit(1)
End If
Dim
 rt As Type = a.GetType()
Dim compunit As New CodeCompileUnit
Dim TheName As New CodeNamespace("TheName")
compunit.Namespaces.Add(TheName)
TheName.Imports.Add(
New CodeNamespaceImport("System"))
Dim Class1 As New CodeTypeDeclaration("Class1")
TheName.Types.Add(Class1)
Dim AddT As New CodeMemberMethod
AddT.ReturnType = 
New CodeTypeReference(rt)
AddT.Parameters.Add(
New CodeParameterDeclarationExpression(rt, "a"))
AddT.Parameters.Add(
New CodeParameterDeclarationExpression(rt, "b"))
AddT.Statements.Add(
New CodeMethodReturnStatement 
(
New CodeSnippetExpression("a+b")))
Class1.TypeAttributes = TypeAttributes.Public
Class1.Members.Add(AddT)
AddT.Attributes = MemberAttributes.Public
AddT.Name = "AddT"
Dim compparams As New CompilerParameters(New String() {"mscorlib.dll"})
compparams.GenerateInMemory = 
True
'compparams.OutputAssembly="factory.dll";
Dim csharp As New Microsoft.CSharp.CSharpCodeProvider
Dim cscompiler As ICodeCompiler = csharp.CreateCompiler()
Dim compresult As CompilerResults = cscompiler.CompileAssemblyFromDom(compparams, compunit)
If compresult Is Nothing Or compresult.Errors.Count > 0 Then
Environment.Exit(1)
End If
Dim
 o As Object = compresult.CompiledAssembly.CreateInstance("TheName.Class1")Dim test As Type = compresult.CompiledAssembly.GetType("TheName.Class1")
Dim m As MethodInfo = test.GetMethod("AddT")
Dim arg(2) As Object
arg(0) = a
arg(1) = b
Dim result As Object = m.Invoke(o, arg)
Return result
End Function 'Mimic
End Class 'T 

Code is self-explanatory and similar to first example. Again if you like you can write assembly to disc by means of doing uncomment of following line:

//compparams.OutputAssembly="factory.dll"

Speed is far from desirable but option to pass different code snippet together with variable if desired makes this approach even more interesting.

In order to gain more speed it is necessary to use System.Reflection.Emit, IL knowledge is also required. Following is my raw code where OpCodes.Add and OpCodes. Add_Ovf (for integer types) should be altered accordingly, also checking if both values making pair are of same type is necessary; if they are not it is possible to try to convert them to same type and if that is not possible, it is always possible to throw exception. Since this code is intended only as illustration and in order to make it shorter I omitted these steps.

Imports System
Imports System.IO
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Threading
Public Class ASM
Public Function build(ByVal currentType As StringAs Type
Dim assemblyName As New AssemblyName
assemblyName.Name = "genAssembly"
Dim [assembly] As AssemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName,AssemblyBuilderAccess.Run)
Dim [module] As ModuleBuilder = [assembly].DefineDynamicModule("genAssembly") ', "genAssembly.dll");
Dim genClass As TypeBuilder = [module].DefineType("Gen", TypeAttributes.Public)Dim valField AsFieldBuilder = genClass.DefineField("_val", Type.GetType currentType), FieldAttributes.Private)
Dim arg(1) As Type
arg(0) = Type.GetType(currentType)
Dim cb As ConstructorBuilder = genClass.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, arg)
Dim ctorIL As ILGenerator = cb.GetILGenerator()
ctorIL.Emit(OpCodes.Ldarg_0)
Dim objectClass As Type = Type.GetType("System.Object")
Dim bc As ConstructorInfo = objectClass.GetConstructor(New Type(0) {})
ctorIL.Emit(OpCodes.Call, bc)
ctorIL.Emit(OpCodes.Ldarg_0)
ctorIL.Emit(OpCodes.Ldarg_1)
ctorIL.Emit(OpCodes.Stfld, valField)
ctorIL.Emit(OpCodes.Ret)
Dim mb As MethodBuilder = genClass.DefineMethod("GetVal", MethodAttributes.Public OrMethodAttributes.HideBySig, Type.GetType
(currentType), 
Nothing)
Dim getValMethod As MethodBuilder = mb
Dim methodIL As ILGenerator = mb.GetILGenerator()
methodIL.Emit(OpCodes.Ldarg_0)
methodIL.Emit(OpCodes.Ldfld, valField)
methodIL.Emit(OpCodes.Ret)
arg = 
New Type(2) {}
arg(0) = genClass
arg(1) = genClass
mb = genClass.DefineMethod("op_Addition", MethodAttributes.Public 
Or MethodAttributes.Static OrMethodAttributes.HideBySig Or MethodAttributes.SpecialName, genClass, arg)
methodIL = mb.GetILGenerator()
methodIL.Emit(OpCodes.Ldarg_0)
methodIL.Emit(OpCodes.Callvirt, getValMethod)
methodIL.Emit(OpCodes.Ldarg_1)
methodIL.Emit(OpCodes.Callvirt, getValMethod)
methodIL.Emit(OpCodes.Add)
methodIL.Emit(OpCodes.Newobj, cb)
methodIL.Emit(OpCodes.Ret)
Dim argTypes(1) As Type
argTypes(0) = Type.GetType(currentType)
Dim convToStrMethod As MethodInfo = GetType(Convert).GetMethod("ToString", argTypes)
arg = 
New Type(1) {}
arg(0) = genClass
mb = genClass.DefineMethod("op_Implicit", MethodAttributes.Public 
Or MethodAttributes.Static OrMethodAttributes.HideBySig Or MethodAttributes.SpecialName, Type.GetType("System.String"), arg)
methodIL = mb.GetILGenerator()
methodIL.Emit(OpCodes.Ldarg_0)
methodIL.Emit(OpCodes.Callvirt, getValMethod)
methodIL.Emit(OpCodes.Call, convToStrMethod)
methodIL.Emit(OpCodes.Ret)
Return genClass.CreateType()
End Function 'build 
End Class 'ASM
Public Class Pair
Private a, b As Object
Private
 current As String
Public
 Sub New(ByVal A As ObjectByVal B As Object)
a = A
b = B
current = A.GetType().ToString()
End Sub 'New
Public Sub Sum()
Dim asm As New ASM
Dim t As Type = asm.build(current)
a = Activator.CreateInstance(t, 
New [Object]() {a})
b = Activator.CreateInstance(t, 
New [Object]() {b})
Dim obj As [Object] = t.InvokeMember("op_Addition", 
BindingFlags.InvokeMethod, 
Nothing, a, New [Object]() {a, b})
obj = t.InvokeMember("GetVal", BindingFlags.InvokeMethod, 
Nothing, obj,Nothing)Console.WriteLine("Type is " + obj.GetType() + " and sum={0}", obj.ToString())
End Sub 'Sum
End Class 'Pair 

Class Pair is a friendly wrapper and saves user from System.Reflection.Emit intricacies. The code is intended to be compiled as library. Strings as pair are not supported; to support them it could be done inside class Pair on high level language. Immediately at the beginning of Sum insert something like this:

If current = "System.String" Then
Console.WriteLine((Convert.ToString(a) + Convert.ToString(b)))
Return
End
 If

Changing "current" from string to type and accordingly "build" method to accept type instead of string eliminates few Type.GetType(currentType) calls but I didn't find any significant performance improvement. So here is code from test app:

Dim p As New Pair(1, 2)
p.Sum()
p = 
New Pair(1.2, 2.4)
p.Sum()
p = 
New Pair(3.2F, 5.4F)
p.Sum()
Console.Read()

Don't forget to reference library vbc /r:<file name> ...
Going from comfort related with writing code in high level language like VB.NET or VB to difficulties related with work in assembly language will be probably big minus for this approach if we are talking about the number of potential users (developers, not final users). But increase in speed is significant, while first example takes (on my machine which is not very fast) around 5 seconds to execute, second executes in 0.2 seconds. Changing significantly the content of the method in the second example will probably result in work similar to that one which is required for writing compiler. But if we restrict number of possible methods it is always possible to prepare number of templates for creation of IL code, so that would make these changes possible to some degree. Combining that with factory pattern could result in something that is not that difficult to work with.


Similar Articles