Introduction
This article describes a .NET (2.0) class that uses Luyan's Project, "A General Fast Method Invoker", to dynamically create delegates and invoke them at runtime.
Background
The C# project by Luyan entitled, A General Fast Method Invoker, is extremely useful for dynamically creating delegates and invoking them. I needed this functionality when I wanted my users to access helpful functions in a Form
class by simply clicking on a list of the functions, and filling in the appropriate function arguments.
Using System.Reflection
, you can obtain a MethodInfo
object for any (or all) of the functions that you have written. The MethodInfo
object allows you to create a delegate at runtime, which you can then invoke to execute the function that the user selected.
Using the code
The code is in one zip file, but divided into two separate projects.
- Unzip the all the entire file into your the same folder that contains your Visual Studio 2005. For me, it was my MyDocuments folder.
- From Visual Studio 2005, open the project FastInvoker (it should be in the folder Visual Studio 2005\Projects\FastInvoker). Build the project. It is a C# Class Library.
- Run another instance of Visual Studio 2005 and open the project SimpleTestForm, which I stupidly put in the folder Visual Studio 2005\Projects\FastInvokeTestForm.
- You might see a broken reference to the
FastInvoker
. I believe that this broken reference will go away after you build the SimpleTestForm project for the first time. (Don't ask me why).
- Look at the code for each project.
The Visual Basic .NET project entitled "SimpleTestForm" instantiates a FastInvoker
object after doing some other useful tricks which might amuse you.
The C# project entitled FastInvoker creates the FastInvoke
class, which creates the dynamic delegate, stores the dynamic delegate and provides a public method to execute the dynamic delegate. I copied most of this code from Luyan. He deserves all of the real credit for the main functionality in this project. I simply copied Luyan's code into a new class, and created a useful constructor that saves the dynamic delegate that he creates, and also saves other useful information about the called function.
The following Visual Basic .NET (2.0) code is for the SimpleTestForm form that tests the FastInvoke
object. (The C# FastInvoke
class code is further below.)
Imports FastInvoke = FastInvoker.FastInvoke
Public Class Form1
Dim FastInvokeObjectList As New List(Of FastInvoke)
Dim methodNames As New List(Of String)
Public Sub New()
InitializeComponent()
Dim i As Integer = 0
Dim methodinfoArray() As _
System.Reflection.MethodInfo = Me.GetType.GetMethods()
For Each m As System.Reflection.MethodInfo In methodinfoArray
If m.DeclaringType Is Me.GetType Then
FastInvokeObjectList.Add(New FastInvoke(Me, m.Name))
methodNames.Add(m.Name)
ListBox1.Items.Add(m.Name)
i = i + 1
End If
Next
End Sub
Private Sub ListBox1_SelectedIndexChanged(ByVal sender As _
System.Object, ByVal e As System.EventArgs) _
Handles ListBox1.SelectedIndexChanged
Dim functionString As String
functionString = ListBox1.Items(ListBox1.SelectedIndex) & "("
For Each p As System.Reflection.ParameterInfo In _
FastInvokeObjectList(ListBox1.SelectedIndex).MyParameters
functionString = functionString & p.ParameterType.Name & ","
Next
functionString = Mid(functionString, 1, _
Len(functionString) - 1) & ")"
TextBox1.Text = functionString
TextBox1.Select()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim TempDouble As Double
Dim Ptype As Type
Dim functioncall As String = Me.TextBox1.Text
Dim PositionOfLeftParen As Integer = InStr(functioncall, "(")
Dim func_name As String = Mid(functioncall, 1, _
PositionOfLeftParen - 1)
Dim i As Integer = 0
For Each name As String In methodNames
If name = func_name Then
Exit For
Else
i = i + 1
End If
Next
If i < methodNames.Count Then
Dim params(FastInvokeObjectList(i).NumberOfArguments_
- 1) As Object
Dim PositionOfComma As Integer
Dim k As Integer = 0
Dim paramtoken As String
For j As Integer = PositionOfLeftParen + 1 To Len(functioncall)
PositionOfComma = InStr(Mid(functioncall, j), ",")
If PositionOfComma > 0 Then
paramtoken = Mid(functioncall, j, PositionOfComma - 1)
Ptype = _
Me.FastInvokeObjectList(i).MyParameters(k).ParameterType
If IsNumeric(paramtoken) Then
TempDouble = CType(paramtoken, Double)
params(k) = _
Me.FastInvokeObjectList(i).TypeConvert(_
TempDouble, Ptype)
Else
params(k) = paramtoken
End If
k = k + 1
j = j + PositionOfComma - 1
Else
paramtoken = Mid(functioncall, j, Len(functioncall) - j)
Ptype = _
Me.FastInvokeObjectList(i).MyParameters(k).ParameterType
If IsNumeric(paramtoken) Then
TempDouble = _
CType(paramtoken, Double)
params(k) = _
Me.FastInvokeObjectList(i).TypeConvert(TempDouble, _
Ptype)
Else
params(k) = paramtoken
End If
Exit For
End If
Next
Try
Dim o As Object = _
FastInvokeObjectList(i).ExecuteDelegate(params)
MsgBox("result = " & o)
Catch ex As Exception
MsgBox(ex.Message)
End Try
Else
MsgBox("bad function name - try again ")
End If
End Sub
Function getmethodtest(ByVal p1 As String, _
ByVal p2 As Integer, ByVal p3 As Double) As String
getmethodtest = p1 & p2 & p3
End Function
Function AddTwoNumbers(ByVal x As Double, _
ByVal y As Double) As Double
AddTwoNumbers = x + y
End Function
Function SaySomething(ByVal s As String) As String
SaySomething = "I would like to say " & s
End Function
End Class
Code for the FastInvoke
class is listed below:
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace FastInvoker
{
public delegate object FastInvokeHandler(object
target, object[] paramters);
public class Class1
{
}
public class FastInvoke
{
FastInvokeHandler MyDelegate;
public MethodInfo MyMethodInfo;
public ParameterInfo [] MyParameters;
Object HostObject;
public int NumberOfArguments;
public FastInvoke(Object MyObject, String MyName)
{
HostObject = MyObject;
Type t2 = MyObject.GetType();
MethodInfo m2 = t2.GetMethod(MyName);
MyDelegate = GetMethodInvoker(m2);
NumberOfArguments = m2.GetParameters().Length;
MyMethodInfo = m2;
MyParameters = m2.GetParameters();
}
public object ExecuteDelegate(object [] FunctionParameters)
{
try
{
return (MyDelegate(HostObject, FunctionParameters));
}
catch(Exception e)
{
Object o = new Object();
o = e.Message;
return (o);
}
}
private FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)
{
DynamicMethod dynamicMethod = new DynamicMethod(string.Empty,
typeof(object), new Type[] { typeof(object),
typeof(object[]) },
methodInfo.DeclaringType.Module);
ILGenerator il = dynamicMethod.GetILGenerator();
ParameterInfo[] ps = methodInfo.GetParameters();
Type[] paramTypes = new Type[ps.Length];
for (int i = 0; i < paramTypes.Length; i++)
{
if (ps[i].ParameterType.IsByRef)
paramTypes[i] = ps[i].ParameterType.GetElementType();
else
paramTypes[i] = ps[i].ParameterType;
}
LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
for (int i = 0; i < paramTypes.Length; i++)
{
locals[i] = il.DeclareLocal(paramTypes[i], true);
}
for (int i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg_1);
EmitFastInt(il, i);
il.Emit(OpCodes.Ldelem_Ref);
EmitCastToReference(il, paramTypes[i]);
il.Emit(OpCodes.Stloc, locals[i]);
}
if (!methodInfo.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
}
for (int i = 0; i < paramTypes.Length; i++)
{
if (ps[i].ParameterType.IsByRef)
il.Emit(OpCodes.Ldloca_S, locals[i]);
else
il.Emit(OpCodes.Ldloc, locals[i]);
}
if (methodInfo.IsStatic)
il.EmitCall(OpCodes.Call, methodInfo, null);
else
il.EmitCall(OpCodes.Callvirt, methodInfo, null);
if (methodInfo.ReturnType == typeof(void))
il.Emit(OpCodes.Ldnull);
else
EmitBoxIfNeeded(il, methodInfo.ReturnType);
for (int i = 0; i < paramTypes.Length; i++)
{
if (ps[i].ParameterType.IsByRef)
{
il.Emit(OpCodes.Ldarg_1);
EmitFastInt(il, i);
il.Emit(OpCodes.Ldloc, locals[i]);
if (locals[i].LocalType.IsValueType)
il.Emit(OpCodes.Box, locals[i].LocalType);
il.Emit(OpCodes.Stelem_Ref);
}
}
il.Emit(OpCodes.Ret);
FastInvokeHandler invoder = (FastInvokeHandler)
dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
return invoder;
}
private static void EmitCastToReference(ILGenerator il,
System.Type type)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, type);
}
else
{
il.Emit(OpCodes.Castclass, type);
}
}
private static void EmitBoxIfNeeded(ILGenerator il, System.Type type)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Box, type);
}
}
private static void EmitFastInt(ILGenerator il, int value)
{
switch (value)
{
case -1:
il.Emit(OpCodes.Ldc_I4_M1);
return;
case 0:
il.Emit(OpCodes.Ldc_I4_0);
return;
case 1:
il.Emit(OpCodes.Ldc_I4_1);
return;
case 2:
il.Emit(OpCodes.Ldc_I4_2);
return;
case 3:
il.Emit(OpCodes.Ldc_I4_3);
return;
case 4:
il.Emit(OpCodes.Ldc_I4_4);
return;
case 5:
il.Emit(OpCodes.Ldc_I4_5);
return;
case 6:
il.Emit(OpCodes.Ldc_I4_6);
return;
case 7:
il.Emit(OpCodes.Ldc_I4_7);
return;
case 8:
il.Emit(OpCodes.Ldc_I4_8);
return;
}
if (value > -129 && value < 128)
{
il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
}
else
{
il.Emit(OpCodes.Ldc_I4, value);
}
}
public object TypeConvert(object source, Type DestType)
{
object NewObject = System.Convert.ChangeType(source,DestType);
return (NewObject);
}
}
}
History
I am currently writing a trading application that requires me to provide a recursive descent parser within my code, so that users can enter formulas for various purposes. I wanted to allow users of the application to enter functions, and I wanted to create an environment where other developers could easily add functions. I realized that the System.Reflection
classes would be helpful, when I stumbled upon Luyan's brilliant code to create a fast method invoker. I decided to wrap his code, so it would be a little more usable. I wanted to send it to him directly, but I couldn't get his email address (he's in China) so I decided to post this code, and then send him a message from his project.
I tried to create code in which, upon instantiating the main form, the constructor code would "pick up" every member function of the form class that was not inherited - in other words, every function that I wrote. I also wanted to use the parameter type information that you can extract from the function/method's MethodInfo
class, so that I could dynamically cast the user's input arguments into the function/method's respective parameter and return types - at runtime.
I was able to accomplish this functionality for all functions that have strings and numbers for their parameters and return type. However, I would have liked to be able to dynamically cast any kind of parameter or return type at runtime, but was unable to figure out a way to do this. I needed a function that accomplished what the Visual Basic statement CType(object, nativeType)
accomplishes, but where the nativeType
can be a System.Type
object. I just could not quite figure this out.