从IL

时间:2016-01-21 22:34:34

标签: c# reflection .net-assembly reflection.emit il

我正在尝试使用反射在运行时创建方法的副本。

我有以下代码。

public static R CopyMethod<T, R>(Func<T, R> f, T t)
{
    AppDomain currentDom = Thread.GetDomain();
    AssemblyName asm = new AssemblyName();
    asm.Name = "DynamicAssembly";
    AssemblyBuilder abl = currentDom.DefineDynamicAssembly(asm, AssemblyBuilderAccess.Run);
    ModuleBuilder mbl = abl.DefineDynamicModule("Module");
    TypeBuilder tbl = mbl.DefineType("Type");
    var info = f.GetMethodInfo();
    MethodBuilder mtbl = tbl.DefineMethod(info.Name, info.Attributes, info.CallingConvention, info.ReturnType, info.GetParameters().Select(x => x.ParameterType).ToArray());

    byte[] il = f.Method.GetMethodBody().GetILAsByteArray();

    mtbl.CreateMethodBody(il, il.Length);
    Type type = tbl.CreateType();
    Func<T, R> method = type.GetMethod(info.Name).CreateDelegate(typeof(Func<T, R>)) as Func<T, R>;
    return method(t);
}

最后一行抛出一条消息:

  

Common Language Runtime检测到无效程序。

还有另一种方法吗?我希望能够获得方法的解析树,而不是直接使用IL。

编辑1

我正在测试以下功能。

public static int Fib(int n)
{
    /*if (n < 2)
        return 1;
    return Fib(n - 1) + Fib(n - 2);*/
    return n;
}

使用以下行进行测试。

int x = Copy.CopyMethod(Copy.Fib, 10);

编辑2

Rob的回答有助于解决上述问题。但是,当使用稍微复杂的Fib()方法时(例如注释的Fibonacci方法),程序会崩溃并显示以下消息。

  

未找到索引。 (HRESULT异常:0x80131124)

编辑3

我已经从评论中尝试了一些建议,但元数据标记不能位于动态程序集中。

public static R CopyMethod<T, R>(Func<T, R> f, T t)
{
    AppDomain currentDom = Thread.GetDomain();
    AssemblyName asm = new AssemblyName("DynamicAssembly");
    AssemblyBuilder abl = currentDom.DefineDynamicAssembly(asm, AssemblyBuilderAccess.Run);
    ModuleBuilder mbl = abl.DefineDynamicModule("Module");
    TypeBuilder tbl = mbl.DefineType("Type");
    MethodInfo info = f.GetMethodInfo();
    MethodBuilder mtbl = tbl.DefineMethod(info.Name, info.Attributes, info.CallingConvention, info.ReturnType, info.GetParameters().Select(x => x.ParameterType).ToArray());
    MethodBody mb = f.Method.GetMethodBody();
    byte[] il = mb.GetILAsByteArray();

    OpCode[] opCodes = GetOpCodes(il);
    Globals.LoadOpCodes();
    MethodBodyReader mbr = new MethodBodyReader(info);
    string code = mbr.GetBodyCode();
    Console.WriteLine(code);

    ILGenerator ilg = mtbl.GetILGenerator();
    ilg.DeclareLocal(typeof(int[]));
    ilg.DeclareLocal(typeof(int));
    for (int i = 0; i < opCodes.Length; ++i)
    {
        if (opCodes[i].OperandType == OperandType.InlineType)
        {
            int token;
            Type tp = info.Module.ResolveType(token = BitConverter.ToInt32(il, i + 1), info.DeclaringType.GetGenericArguments(), info.GetGenericArguments());
            ilg.Emit(opCodes[i], tp.MetadataToken);
            i += 4;
            continue;
        }
        if (opCodes[i].FlowControl == FlowControl.Call)
        {
            int token;
            MethodBase mi = info.Module.ResolveMethod(token = BitConverter.ToInt32(il, i + 1));
            ilg.Emit(opCodes[i], mi.MetadataToken);
            i += 4;
            continue;
        }
        ilg.Emit(opCodes[i]);
    }

    Type type = tbl.CreateType();
    Func<T, R> method = type.GetMethod(info.Name).CreateDelegate(typeof(Func<T, R>)) as Func<T, R>;
    return method(t);
}

以下也不起作用。

var sigHelp = SignatureHelper.GetLocalVarSigHelper(mtbl.Module);
mtbl.SetMethodBody(il, mb.MaxStackSize, sigHelp.GetSignature(), null, new int[] { 3 });

我可以通过以下方式更改元数据标记来修复递归函数调用(我意识到这在所有情况下都不起作用,但我试图让它以某种方式工作)。

if (opCodes[i].FlowControl == FlowControl.Call)
{
    ilg.Emit(opCodes[i], mtbl);
    i += 4;
}

我可以使用相关问题的答案中建议的方法构建动态方法:Reference a collection from IL constructed method。但是,当尝试在此处执行相同操作时,它会失败。

2 个答案:

答案 0 :(得分:3)

我设法根据评论中非常有用的讨论来实施重建。它没有解决所有可能的情况,但很好地说明了解决方案。

public static R CopyMethod<T, R>(Func<T, R> f, T t)
{
    AppDomain currentDom = Thread.GetDomain();
    AssemblyName asm = new AssemblyName("DynamicAssembly");
    AssemblyBuilder abl = currentDom.DefineDynamicAssembly(asm, AssemblyBuilderAccess.Run);
    ModuleBuilder mbl = abl.DefineDynamicModule("Module");
    TypeBuilder tbl = mbl.DefineType("Type");
    MethodInfo info = f.GetMethodInfo();
    MethodBuilder mtbl = tbl.DefineMethod(info.Name, info.Attributes, info.CallingConvention, info.ReturnType, info.GetParameters().Select(x => x.ParameterType).ToArray());
    MethodBody mb = f.Method.GetMethodBody();
    byte[] il = mb.GetILAsByteArray();
    ILGenerator ilg = mtbl.GetILGenerator();
    foreach (var local in mb.LocalVariables)
        ilg.DeclareLocal(local.LocalType);
    for (int i = 0; i < opCodes.Length; ++i)
    {
        if (!opCodes[i].code.HasValue)
            continue;
        OpCode opCode = opCodes[i].code.Value;
        if (opCode.OperandType == OperandType.InlineBrTarget)
        {
            ilg.Emit(opCode, BitConverter.ToInt32(il, i + 1));
            i += 4;
            continue;
        }
        if (opCode.OperandType == OperandType.ShortInlineBrTarget)
        {
            ilg.Emit(opCode, il[i + 1]);
            ++i;
            continue;
        }
        if (opCode.OperandType == OperandType.InlineType)
        {
            Type tp = info.Module.ResolveType(BitConverter.ToInt32(il, i + 1), info.DeclaringType.GetGenericArguments(), info.GetGenericArguments());
            ilg.Emit(opCode, tp);
            i += 4;
            continue;
        }
        if (opCode.FlowControl == FlowControl.Call)
        {
            MethodInfo mi = info.Module.ResolveMethod(BitConverter.ToInt32(il, i + 1)) as MethodInfo;
            if (mi == info)
                ilg.Emit(opCode, mtbl);
            else
                ilg.Emit(opCode, mi);
            i += 4;
            continue;
        }
        ilg.Emit(opCode);
    }

    Type type = tbl.CreateType();
    Func<T, R> method = type.GetMethod(info.Name).CreateDelegate(typeof(Func<T, R>)) as Func<T, R>;
    return method(t);
}

static OpCodeContainer[] GetOpCodes(byte[] data)
{
    List<OpCodeContainer> opCodes = new List<OpCodeContainer>();
    foreach (byte opCodeByte in data)
        opCodes.Add(new OpCodeContainer(opCodeByte));
    return opCodes.ToArray();
}

class OpCodeContainer
{
    public OpCode? code;
    byte data;

    public OpCodeContainer(byte opCode)
    {
        data = opCode;
        try
        {
            code = (OpCode)typeof(OpCodes).GetFields().First(t => ((OpCode)(t.GetValue(null))).Value == opCode).GetValue(null);
        }
        catch { }
    }
}

答案 1 :(得分:0)

Igor提供的有用解决方案的问题在于,它对传递给函数的信息使用ResolveMethod。这意味着它将把克隆的实例强制转换为原始类型(不应该允许,但我们在IL中!),然后调用原始方法。例如如果我的原始类中有两个方法#include <stdio.h> /** @return 1 if c is whitespace, else 0. */ static int isWhite(int c); /** When this function returns, c is either EOF or non-whitespace. @return c */ static int readWhite(); /** When this function returns, c is either EOF or whitespace. @return c */ static int readWord(); int main(void) { int wordcount = 0; while(readWhite() != EOF) { wordcount++; readWord(); } printf("%d\n", wordcount); } ////////////////////////////////////////////////////// static int isWhite(int c) { return c == '\n' || c == '\t' || c == ' '; } ////////////////////////////////////////////////////// static int readWord() { int c; while((c = getchar()) != EOF && !isWhite(c)) { ; // this statement intentionally left blank } return c; } ////////////////////////////////////////////////////// static int readWhite() { int c; while((c = getchar()) != EOF && isWhite(c)) { ; // this statement intentionally left blank } return c; } ,分别称为TestClassSimpleMethod,则复制的类型将执行以下操作:

MethodCallingSimpleMethod

要完全实现这一点,我们需要找到方法之间的依赖关系。按正确的顺序复制它们,然后使用元令牌解析为原始的MethodInfo,然后以新类型查找已复制的方法信息。

不平凡。

字段需要做类似的事情,但是更简单,因为我们可以先创建字段,然后再引用它们的方法。