从Action <t>指令创建DynamicMethod

时间:2016-10-20 09:11:00

标签: c# reflection dynamicmethod

我正在使用DynamicMethod并且目标是执行以下操作:

我有一个Action,我使用GetILAsByteArray()从中获取IL代码作为字节。从这个字节我想创建一个Dynamic方法并执行is。这是我想要做的一个例子:

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是由Haibo Luo开发的类,在blog post中描述,但也可以直接下载here

1 个答案:

答案 0 :(得分:3)

你可以这样做:

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

ILReader是一个为你努力工作的课程。您可以从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构造函数的paramters数组的第一个单元格中传递方法的instacne类型。

<强>更新

由于null不是静态方法,因此您无法在dm.Invoke(**null**, new object[] { "World" });传递myAction

myActionAction<string>)实际上是一个新生成的类中保存该方法的方法。

但是我检查过,即使我传递myAction.Target或该类型的新实例,异常也会抛出。例外(CLR dedect一个无效的程序)告诉你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构造函数的paramters数组的第一个单元格中传递方法的instacne类型。

所以你需要这样做:

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

现在它的工作非常完美。