Search code examples
c#reflection.net-assemblyreflection.emitil

Create a copy of method from IL


I am trying to create a copy of a method during runtime using reflection.

I have the following code.

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

The last line throws an exception with message:

Common Language Runtime detected an invalid program.

Is there another way of doing this? I would prefer being able to get the parse tree of the method instead of using IL directly.

EDIT 1:

I am testing with the following function.

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

Testing with the following line.

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

EDIT 2:

Rob's answer helps address the above issue. However, when using the Fib() method that is slightly more complicated (e.g. the commented Fibonacci method), the program crashes with the following message.

Index not found. (Exception from HRESULT: 0x80131124)

EDIT 3:

I have tried several suggestions from comments, but the metadata token cannot be located within the dynamic assembly.

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

The following also does not work.

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

I can fix the recursive function calls by changing the metadata token the following way (I realize that this will not work in all cases, but I am trying to get it to work in some way).

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

I can build a dynamic method using the approach suggested in the answer to the related question: Reference a collection from IL constructed method. However, when trying to do the same here, it fails.


Solution

  • I managed to implement the reconstruction based on the very helpful discussion in the comments. It does not address all possible scenarios, but illustrates the solution very well.

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