Sjoerd222888 Sjoerd222888 - 1 month ago 17
C# Question

Create DynamicMethod from Action<T> instructions

I am playing around with DynamicMethod and aim to do the following:

I have an Action from which I obtain the IL code as bytes using

GetILAsByteArray()
. From this bytes I would like to create a Dynamic method and execute is. Here an example of what I am trying to do:

class Program
{
static void Main(string[] args)
{
//Create action and execute
Action<string> myAction = s =>
{
Console.WriteLine("Hello " + s);
};
myAction("World");
//Get IL bytes
byte[] ilBytes = myAction.GetMethodInfo().GetMethodBody().GetILAsByteArray();
DynamicMethod dynamicCallback = new DynamicMethod("myAction", typeof(void), new Type[] { typeof(string) });
DynamicILInfo dynamicIlInfo = dynamicCallback.GetDynamicILInfo();
dynamicIlInfo.SetCode(ilBytes, 100);
dynamicCallback.Invoke(null, new object[] { "World" });
}
}


When calling a
dynamicCallback.Invoke(null, new object[] { "World" })
we get "Exception thrown: 'System.BadImageFormatException' in mscorlib.dll".

One thing I have no idea abut is what I should use as second argument for
SetCode()
, what should be used as 'maxStackSize'? How can I set the same value as for the initial action? But I suppose this is not the reason for the exception.

How can I properly create a dynamic method from the IL bytes?




Solution

Here I would like to summarize the complete solution provided by Dudi Keleti:

static void Main(string[] args)
{
Action<string> myAction = s =>
{
Console.WriteLine("Hello " + s);
};
MethodInfo method = myAction.GetMethodInfo();
object target = myAction.Target;

DynamicMethod dm = new DynamicMethod(
method.Name,
method.ReturnType,
new[] {method.DeclaringType}.
Concat(method.GetParameters().
Select(pi => pi.ParameterType)).ToArray(),
method.DeclaringType,
skipVisibility: true);

DynamicILInfo ilInfo = dm.GetDynamicILInfo();
var body = method.GetMethodBody();
SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper();
foreach (LocalVariableInfo lvi in body.LocalVariables)
{
sig.AddArgument(lvi.LocalType, lvi.IsPinned);
}
ilInfo.SetLocalSignature(sig.GetSignature());
byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
DynamicMethodHelper.ILInfoGetTokenVisitor visitor = new DynamicMethodHelper.ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);

dm.Invoke(target, new object[] { target, "World" });

Console.ReadLine(); //Just to see the result
}


Note: DynamicMethodHelper is class developed by Haibo Luo and described in a blog post but can also be downloaded directly here.

Answer

You can do it like this:

byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);

ILReader is a class that do the hard work for you. You can copy it from here.

Example:

MethodInfo method = ...
DynamicMethod dm = new DynamicMethod(
     method.Name,
     method.ReturnType,
     method.GetParameters.Select(pi => pi.ParameterType).ToArray(),
     method.DeclaringType,
     skipVisibility: true\fasle - depends of your need);

DynamicILInfo ilInfo = dm.GetDynamicILInfo();
var body = method.GetMethodBody();
SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper();
foreach(LocalVariableInfo lvi in body.LocalVariables)
{
    sig.AddArgument(lvi.LocalType, lvi.IsPinned);
}
ilInfo.SetLocalSignature(sig.GetSignature());
byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);

If your method is a simple method (not generic and without exception handles), thid should work.

If your method is a generic one, you need to do this for passing the owner type to the DynamicMethod constructor:

var owner = method.DeclaringType.MakeGenericType(
             method.DeclaringType.GetGenericArguments());

One more thing, if its still not working, and your method is an instance method, pass the instacne type of the method in the first cell of the paramters array of the DynamicMethod constructor.

Update

You can't pass null here dm.Invoke(**null**, new object[] { "World" }); because myAction is not a static method.

myAction (Action<string>) is actually a method in a new generated class that hold that method.

But I checked and the exception is throwing even if I pass myAction.Target or a new instance of that type. The exception (CLR dedect an invalid program) is telling you that the IL is not exactly correct. I can't tell you now exactly what the problem but if it's important to you, I can check it next week when I'll get back to work.

Anyway if you just want to see the DynamicIlInfo.SetCode in action you can use your code as is but just change the method info from this:

class Program
{        
    static void Main(string[] args)
    {
        Action<string> myAction = s =>
        {
            Console.WriteLine("Hello " + s);
        };
        MethodInfo method = myAction.GetMethodInfo();

        //Rest of your code
    }
}

to this:

class Program
{
    static void M(string s)
    {
        Console.WriteLine("Hello " + s);
    }

    static void Main(string[] args)
    {
        MethodInfo method = typeof (Program).GetMethod("M", BindingFlags.Static | BindingFlags.NonPublic);

        //Rest of your code
    }
}

Update 2:

Apparently I was very tired yesterday, I did not realize your mistake.

As I wrote in my original answer,

One more thing, if its still not working, and your method is an instance method, pass the instacne type of the method in the first cell of the paramters array of the DynamicMethod constructor.

So you need to do this:

DynamicMethod dm = new DynamicMethod(
     method.Name,
     method.ReturnType,
     new[] {method.DeclaringType}.
        Concat(method.GetParameters().
        Select(pi => pi.ParameterType)).ToArray(),
     method.DeclaringType,
     skipVisibility: true);

And invoke the dynamic method like this:

dm.Invoke(myAction.Target, new object[] { myAction.Target, "World" });

Now it's work perfect.