3

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.

Sjoerd222888
  • 3,228
  • 3
  • 31
  • 64
  • I think you can not get the maxStackSize value using reflection. But indeed, thats not the problem here. The problem is that the `Console.WriteLine` call is encoded as a metadata token (MethodRef probably) and metadata tokens are only valid in the scope of the module declaring it. Have a look at the `DynamicILInfo.GetTokenFor` functions, these will import other metadata items und create tokens valid for the `DynamicMethod`. – thehennyy Oct 20 '16 at 09:22
  • @thehennyy I have tried with no success, see my edit. – Sjoerd222888 Oct 20 '16 at 09:36
  • You have to replace the old token in the IL byte array with the newly created one that the `GetTokenFor` method returns to you. – thehennyy Oct 20 '16 at 09:42
  • I thought already it does not make sense what I coded, I didn't get it immediately. Thx. Is there a general way to do this without needing to know what was called in the action? E.g. is there a way to this dynamically at the end? – Sjoerd222888 Oct 20 '16 at 09:45
  • Yes, you can parse the method body byte array and then resolve all tokens using the `Module.Resolvexxx` methods. – thehennyy Oct 20 '16 at 09:55
  • Why are you trying to do this? As explained by thehennyy, the simple approach won't work because of metadata tokens. Maybe there is some other way to achieve what you're trying to do. – svick Oct 20 '16 at 14:15
  • The question has been posted out of curiosity. What could be done with this is some sort of serialization of methods, which sounds like an interesting thing to play around with. But I know that this might not be a wise thing to do in real applications. – Sjoerd222888 Oct 21 '16 at 07:27

1 Answers1

4

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.

Dudi Keleti
  • 2,946
  • 18
  • 33
  • Where is `ILInfoGetTokenVisitor` declared? – Sjoerd222888 Oct 26 '16 at 12:39
  • @Sjoerd222888 [Here.](https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/01/02/35/08/DynamicMethodHelper.cs) And there is a blog post about it [here](https://blogs.msdn.microsoft.com/haibo_luo/2006/11/07/turn-methodinfo-to-dynamicmethod/) – Dudi Keleti Oct 26 '16 at 12:53
  • I immediately get a 'System.Reflection.TargetInvocationException' with message: "{"Bad binary signature. (Exception from HRESULT: 0x80131192)"}" when trying to invoke the dynamic method. I guess I miss something fundamental. – Sjoerd222888 Oct 26 '16 at 13:35
  • See my update. I still get a `TargetInvocationException`. Where am I going wrong? – Sjoerd222888 Oct 27 '16 at 12:05
  • @Sjoerd222888 Check it now – Dudi Keleti Oct 27 '16 at 23:40
  • Thank you very much for the detailed explanations. I'm just about to learn to work with `DynamicMethod` and your answer is a great help! – Sjoerd222888 Oct 28 '16 at 09:14