从 Action<T> 指令创建 DynamicMethod

Create DynamicMethod from Action<T> instructions

我正在尝试使用 DynamicMethod 并打算执行以下操作:

我有一个操作,使用 GetILAsByteArray() 从中获取字节形式的 IL 代码。从这个字节我想创建一个动态方法并执行它。这是我正在尝试做的一个例子:

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" });
    }
}

当调用 dynamicCallback.Invoke(null, new object[] { "World" }) 时,我们得到 "Exception thrown: 'System.BadImageFormatException' in mscorlib.dll"。

我不知道的一件事是我应该将什么用作 SetCode() 的第二个参数,什么应该用作 'maxStackSize'?如何设置与初始操作相同的值?但我想这不是例外的原因。

如何从 IL 字节正确创建动态方法?


解决方案

这里总结一下杜迪克乐提提供的完整解决方案:

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
}

注意:DynamicMethodHelper class 由 Haibo Luo 开发并在 blog post but can also be downloaded directly here.

中描述

你可以这样做:

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

ILReader 是一个 class 为你做艰苦的工作。您可以从 here.

复制它

示例:

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);

如果您的方法是一个简单的方法(不是通用的并且没有异常句柄),那么应该可以。

如果您的方法是通用方法,则需要执行此操作以将所有者类型传递给 DynamicMethod 构造函数:

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

还有一件事,如果它仍然不起作用,并且您的方法是实例方法,请在 DynamicMethod 构造函数的参数数组的第一个单元格中传递方法的实例类型。

更新

你不能在这里传递 null dm.Invoke(**null**, new object[] { "World" }); 因为 myAction 不是静态方法。

myAction (Action<string>) 实际上是新生成的 class 中的一个方法,其中包含该方法。

但我检查过,即使我传递了 myAction.Target 或该类型的新实例,也会抛出异常。异常(CLR 检测到无效程序)告诉您 IL 不完全正确。我现在不能告诉你到底是什么问题,但如果它对你很重要,我可以在下周回去工作时检查它。

无论如何,如果您只想查看 DynamicIlInfo.SetCode 的实际效果,您可以按原样使用您的代码,但只需更改方法信息:

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

        //Rest of your code
    }
}

对此:

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
    }
}

更新二:

看来我昨天很累,我没有意识到你的错误。

正如我在原来的回答中所写,

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.

所以你需要这样做:

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

并像这样调用动态方法:

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

现在完美了。