//
you're reading...
.NET, Reflection

DynamicMethod as a Viable Alternative to Type.MemberInvoke() and MethodInfo.Invoke()

1. Introduction.

1.1 When using Type.InvokeMember() or MethodInfo.Invoke() to call a method that takes a reference parameter, the referenced object in the arguments array will be updated by the method. However, the arguments array items cannot be used to hold references to existing objects.

1.2 Hence after the method call, the arguments array items will be updated with potentially new values but the original objects will not get updated.

1.3 I will illustrate the problem using an example C# class :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestCSApp01
{
    public class TestClass01
    {
        public void test_string(ref string var)
        {
            var = var + " world";
        }

        public void test_integer(ref int var)
        {
            var = var + 10;
        }
    }
}

The above code is then compiled into an exe named “TestCSApp01.exe”.

The code below shows another C# application that attempts to dynamically create an instance of “TestClass01” and then invoke its “test_string()” method :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;

namespace DynamicCallerApp
{
    class Program
    {
        static void MakePlainOldMethodInvokeCall()
        {
            Assembly assembly = Assembly.LoadFrom(@"..\..\..\TestCSApp01\bin\Debug\TestCSApp01.exe");
            Type type = assembly.GetType("TestCSApp01.TestClass01");
            // Create an instance of TestCSApp01.TestClass01.
            object obj = Activator.CreateInstance(type);
            // Declare parameters to the TestClass01.test() method.
            string var = "hello";
            // Declare array of parameters to the TestClass01.test()
            // method to be used in the Type.InvokeMember() call.
            object[] parameters = new object[1];
            parameters[0] = var;
            // Make the call to TestClass01.test() using "obj".
            type.InvokeMember("test_string", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, obj, parameters);
            // After the method call, parameters[0] is updated but unfortunately, "var" is not updated.
        }

        static void Main(string[] args)
        {
            MakePlainOldMethodInvokeCall();
        }
    }
}

The eventual result is that “var” (the string local variable) remains as “hello” while “parameters[0]” gets updated to become “hello world”.

1.4 This blog expounds on a technique that seeks to overcome this problem using the DynamicMethod class which is recommended by Peter Ritchie in his blog :

Overcoming problems with MethodInfo.Invoke of methods with by-reference value type arguments

1.5 Peter Ritchie’s DynamicMethod technique is a good viable alternative to using Type.InvokeMember(). He basically creates an in-memory member function at runtime. The execution codes for this function are specified directly using intermediate language. In other words, memory will be created which will contain IL code for a function. This function “memory stub” can then be invoked using a delegate.

1.6 This blog builts on this idea proposed by Peter Ritchie and seeks to explain the principles behind it. I will also endeavour to generalize it and emerge an enhanced generic version.

2. A DynamicMethod() Implementation for the test_string() Method.

2.1 Listed below is a sample function for calling the test_string() function using the DynamicMethod technique :

static void DynamicMethodCall(object obj, Type type, ref string value)
{
  // create a dynamic method object with arbitrary name "Caller"
  // that returns void and takes an object parameter and a
  // string parameter that is passed by reference.
  DynamicMethod caller = new DynamicMethod
  (
    "Caller",
    null,
    new Type[] { typeof(object), typeof(string).MakeByRefType() },
    typeof(Program).Module
  );
  ILGenerator ilGenerator = caller.GetILGenerator();

  // Create the IL code that will perform the execution
  // of calling the "test_string()" method from an
  // instance of "TestCSApp01.TestClass01".

  // emit ldarg.0
  ilGenerator.Emit(OpCodes.Ldarg_0);
  // emit ldarg.1
  ilGenerator.Emit(OpCodes.Ldarg_1);
  // emit call instance void TestCSApp01.TestClass01::test_string(string&)
  MethodInfo mi = type.GetMethod
  (
    "test_string",
    BindingFlags.Instance | BindingFlags.Public,
    null,
    new Type[] { typeof(string).MakeByRefType() },
    null
  );
  ilGenerator.Emit(OpCodes.Callvirt, mi);
  // emit ret
  ilGenerator.Emit(OpCodes.Ret);

  OneRefParameterCallback<string> callback
    = (OneRefParameterCallback<string>)caller.CreateDelegate
        (typeof(OneRefParameterCallback<string>));

  // call our emitted code with a reference parameter
  callback(obj, ref value);
}

2.2 The following is a sample call to this method :

static void MakeDynamicMethodCall()
{
  Assembly assembly = Assembly.LoadFrom(@"..\..\..\TestCSApp01\bin\Debug\TestCSApp01.exe");
  Type type = assembly.GetType("TestCSApp01.TestClass01");
  // Create an instance of TestCSApp01.TestClass01.
  object obj = Activator.CreateInstance(type);
  // Declare parameter to the TestClass01.test() method.
  string var = "hello";
  // Make the dynamic call to "test_string()".
  // When this dynamic call completes, "var"
  // will be updated to "hello string".
  DynamicMethodCall(obj, type, ref var);
}

2.3 The above code is dependent on the following superb delegate definition :

// delegate for void method that takes an object type paramater
// and one reference parameter the type of which may be set
// generically.
public delegate void OneRefParameterCallback<T>(object obj, ref T value);

This delegate defines a function pointer that takes as parameters an object type and a reference to another object the type of which can be specified generically.

2.4 Section 3 below provides in-depth analysis behind the working of the dynamic method call.

3. The General Principle the DynamicMethod() Technique.

3.1 The basic aim behind the DynamicMethod technique is to generate the appropriate IL code for dynamic invokation. This is accomplished using the ILGenerator.Emit() method as seen in the DynamicMethodCall() function (point 2.1).

3.2 How do we know what IL code to emit ? One very simple way to look at it is to think of a simple function that makes a call to the “test_string()” method from an instance of “TestClass01”. Here is an example :

static private void test_string_call_stub(TestClass01 obj, ref string str)
{
  obj.test_string(ref str);
}

I have defined the above method as part of the “TestCSApp01.TestClass01” class and I have specified it as being static because it need not be an instance method and it simplifies matters.

3.3 The idea now is to compile the “TestCSApp01.TestClass01” code and then examine the IL code emitted for the call_test_string() static method. This can easily be done via ILDASM.exe. A listing of this is provided below :

.method private hidebysig static void  test_string_call_stub(class TestCSApp01.TestClass01 obj,
                                                               string& str) cil managed
{
  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.1
  IL_0003:  callvirt   instance void TestCSApp01.TestClass01::test_string(string&)
  IL_0008:  nop
  IL_0009:  ret
} // end of method TestClass01::test_string_call_stub

3.4 Compare the above IL code with the IL code which is emitted by DynamicMethodCall() :

// Create the IL code that will perform the execution
// of calling the "test_string()" method from an
// instance of "TestCSApp01.TestClass01".

// emit ldarg.0
ilGenerator.Emit(OpCodes.Ldarg_0);
// emit ldarg.1
ilGenerator.Emit(OpCodes.Ldarg_1);
// emit call instance void TestCSApp01.TestClass01::test_string(string&)
MethodInfo mi = type.GetMethod
(
  "test_string",
  BindingFlags.Instance | BindingFlags.Public,
  null,
  new Type[] { typeof(string).MakeByRefType() },
  null
);
ilGenerator.Emit(OpCodes.Callvirt, mi);
// emit ret
ilGenerator.Emit(OpCodes.Ret);

3.5 Except for the omission of “nop” calls, the similarity can easily be observed.

3.6 Once DynamicMethodCall() has generated such an in-memory function, we simply use a delegate to call it, passing along an instance of a “TestClass01” class and a string parameter by reference :

OneRefParameterCallback callback
  = (OneRefParameterCallback)caller.CreateDelegate
    (typeof(OneRefParameterCallback));

// call our emitted code with a reference parameter
callback(obj, ref value);

The end result will be as if “TestClass01.test_string_call_stub()” was called. However, the call is made in the context of the test application.

4. Making a Generic Version of DynamicMethodCall().

4.1 What if we want to make dynamic method calls to other “TestClass01” methods that take different parameter types passed by reference ? For example :

namespace TestCSApp01
{
    public class TestClass01
    {
        ...

        public void test_integer(ref int var)
        {
            var = var + 10;
        }

        ...

    }
}

Of course, we can endeavour to create multiple versions of DynamicMethodCall() each of which calls a different “TestClass01” method.

4.2 However, a better way is to create a generic dynamic method calling function. After some experimentation and observation of IL code emitted for various methods, I emerged the following generic dynamic method calling function :

static void GenericDynamicMethodCall<T>(object obj, Type type, string strMethodName, ref T value)
{
  // create a dynamic method object with arbitrary name "Caller"
  // that returns void and takes an object parameter and another
  // object parameter the type of which can be generically
  // specified.
  DynamicMethod caller = new DynamicMethod
    (
      "Caller",
      null,
      new Type[] { typeof(object), typeof(T).MakeByRefType() },
      typeof(Program).Module
     );
  ILGenerator ilGenerator = caller.GetILGenerator();

  // emit ldarg.0
  ilGenerator.Emit(OpCodes.Ldarg_0);
  // emit ldarg.1
  ilGenerator.Emit(OpCodes.Ldarg_1);
  // emit call instance void TestCSApp01.TestClass01::(T&)
  MethodInfo mi = type.GetMethod
    (
      strMethodName,
      BindingFlags.Instance | BindingFlags.Public,
      null,
      new Type[] { typeof(T).MakeByRefType() },
      null
    );
  ilGenerator.Emit(OpCodes.Callvirt, mi);
  // emit ret
  ilGenerator.Emit(OpCodes.Ret);

  OneRefParameterCallback<T> callback
    = (OneRefParameterCallback<T>)caller.CreateDelegate
      (typeof(OneRefParameterCallback<T>));

  // call our emitted code with a reference parameter
  callback(obj, ref value);
}

I found that by keeping the memory model of code stubs like test_string_call_stub() [point 3.2] simple, calls to instance methods (that return void and that takes a reference parameter to various types) are consistently similar.

This led me to the development of GenericDynamicMethodCall<T>() with a string parameter that indicates the method to call and a generic type parameter T that indicates the type of the referenced parameter.

4.3 The following method demonstrates 2 calls to GenericDynamicMethodCall<T>(), one to TestClass01.test_string() and the other to TestClass01.test_integer() :

static void MakeGenericDynamicMethodCall()
{
  Assembly assembly = Assembly.LoadFrom(@"..\..\..\TestCSApp01\bin\Debug\TestCSApp01.exe");
  Type type = assembly.GetType("TestCSApp01.TestClass01");
  // Create an instance of TestCSApp01.TestClass01.
  object obj = Activator.CreateInstance(type);
  // Declare parameters to the TestClass01.test() method.
  int var = 10;
  GenericDynamicMethodCall<int>(obj, type, "test_integer", ref var);

  string str = "My";
  GenericDynamicMethodCall<string>(obj, type, "test_string", ref str);
}

5. In Conclusion.

5.1 Kudos and many thanks to Peter Ritchie for introducing us to the concept of dynamic method calls as a viable alternative to Type.InvokeMember() and MethodInfo.Invoke() for calling instance methods that take parameters passed by reference.

5.2 I hope GenericDynamicMethodCall<T>() can serve as an enhancement to his technique. I urge the reader to stress test GenericDynamicMethodCall<T>() for robustness. Please notify me of any possible exceptional circumstance in which it will not work.

5.3 Drop me a comment if any reader has any good ideas for further enhancing GenericDynamicMethodCall<T>().

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: