3

我正在玩 DynamicMethod 并旨在执行以下操作:

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

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" })我们得到“异常抛出:mscorlib.dll 中的'System.BadImageFormatException'”。

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

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


解决方案

这里我想总结一下 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
}

注意:DynamicMethodHelper 是罗海波开发的类,在文中有描述,也可以直接在这里下载。

4

1 回答 1

4

你可以这样做:

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

ILReader是一个为你做艰苦工作的班级。你可以从这里复制它。

例子:

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>) 实际上是新生成的类中包含该方法的方法。

但是我检查了一下,即使我通过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
    }
}

更新 2:

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

正如我在原始答案中所写,

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

所以你需要这样做:

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

现在它的工作完美。

于 2016-10-26T09:24:48.017 回答