8

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.

Community
  • 1
  • 1
Igor Ševo
  • 5,459
  • 3
  • 35
  • 80
  • You can not copy a methodbody like this when a instruction in it uses a metadata token in some way. – thehennyy Jan 21 '16 at 22:37
  • @thehennyy Can you suggest a fix? – Igor Ševo Jan 21 '16 at 22:40
  • @thehennyy When examining the IL taken from `GetILAsByteArray`, there does not appear to be any reference to the token. – Rob Jan 21 '16 at 22:51
  • Can you expand your code with the things you try exactly, so i can have a look at it? – thehennyy Jan 21 '16 at 23:03
  • 2
    The problem is, under debug conditions the compiler introduces a local variable. This local has to be declared with the ILGenerator.DeclareLocal Method or it will not be present and will result in an InvalidProgramException. – thehennyy Jan 22 '16 at 00:03
  • @thehennyy I have tried your suggestion, but it too results in an error. I assume the type of the variable I specify is wrong. – Igor Ševo Jan 22 '16 at 08:03
  • @Rob Why did you delete the answer? It was very useful. – Igor Ševo Jan 22 '16 at 08:08
  • @IgorŠevo As @BrianReichie pointed out, my conclusion was wrong (in that the branch operation takes an *offset* rather than an explicit address to jump to). With that knowledge in the open, the debug `IL` is perfectly valid so the answer that the IL was malformed is wrong, and I don't want to mislead future readers. If you'd like, I can put the code up in a pastebin snippet in the comments, but I don't believe it's suitable as an answer – Rob Jan 22 '16 at 08:12
  • @Rob Yes, but the code resolved several issues. Maybe you could link it. – Igor Ševo Jan 22 '16 at 08:17
  • 1
    @IgorŠevo Here you go :) http://pastebin.com/9hJRp7q0 – Rob Jan 22 '16 at 08:24
  • 2
    As a general notice, i suggest you to specify `AssemblyBuilderAccess.RunAndSave` so you can save your created assembly to the disk. Next you can use [Peverify.exe](https://msdn.microsoft.com/en-us/library/62bwd2yd%28v=vs.110%29.aspx) to verify that everything is ok. Your `Fib` function will not run because it contains a metadata token in the call instruction. – thehennyy Jan 22 '16 at 09:04
  • 1
    As @thehennyy says, your code won't work because it contains metadata tokens. You would need to query the assembly to find out what each metadata token refers to, and somehow replace those references. As an example, this is what one of the recursive calls looks like in the IL bytes: `IL_0010: /* 28 | (06)000002 */ call int32 ConsoleApplication1.Program::Fib(int32)`. You need to find the 06000002, ask the assembly what that is, and replace it with your own metadata token value in the context of your new method. – elchido Jan 22 '16 at 15:28
  • @elchido I have been able to do that with the recursive calls to the method. However, I am having problems with calls to the API (e.g. calls to lists, arrays, dictionaries etc.). I need to substitute those too, but am not sure how to achieve that. – Igor Ševo Jan 22 '16 at 15:32
  • @IgorŠevo I've never done it, but I'd think it's the same: take the metadata token, find out what it refers to in the original assembly, get its `MemberInfo` in your program, and the metadata token out of the `MemberInfo`. – elchido Jan 22 '16 at 17:40
  • @elchido I think I tried that among many other things. I'll give it a try one more time. – Igor Ševo Jan 22 '16 at 17:50
  • @elchido I've tried what you suggested, but it fails. Maybe I am doing it incorrectly. I've updated the question with what I tried. – Igor Ševo Jan 24 '16 at 21:42
  • @thehennyy I tried implementing the answer you had to my other question here, but it seems to not work in this context. – Igor Ševo Jan 24 '16 at 21:46
  • 2
    @IgorŠevo `ILGenerator.Emit()` never takes a metadata token as argument because these tokens have to be generated by the dynamic assembly itself, a metadata token is only valid in the scope of its module. Instead you will always pass the `xxxInfo` or `xxxBuilder` instance. To make things a bit more clear, in the most cases, the xxxBuilder class is a subclass of its corresponding xxxInfo class and so can be used. Keep that in mind when your emitted code gets more complex and involves other dynamic generated members. – thehennyy Jan 24 '16 at 22:55
  • 1
    @IgorŠevo If the method token is for the recursive method you want to replace, emit a call using your `MethodBuilder` object `mtbl`. Otherwise get the `MethodInfo` for the method and emit a call with it. – elchido Jan 25 '16 at 15:03

2 Answers2

5

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 { }
    }
}
Igor Ševo
  • 5,459
  • 3
  • 35
  • 80
2

The problem with the helpful solution from Igor is that it uses ResolveMethod on the info passed to the function. This means that it will be casting the cloned instance to the original type (which shouldn't be allowed but we're in IL!) and then calling the original method. e.g. if I have two methods in my original class, TestClass, called SimpleMethod and MethodCallingSimpleMethod then the copied type will do something like this:

internal class Type
{
  public int SimpleMethod([In] int obj0, [In] string obj1)
  {
    return obj0 + obj1.Length;
  }

  public int MethodCallingSimpleMethod([In] string obj0)
  {
    if (string.IsNullOrEmpty(obj0))
      return 0;
    return ((TestClass) this).SimpleMethod(42, obj0);
  }
}

To implement this fully we'd need to find dependencies between methods. Copy them in the correct order and then use the meta-token to resolve to the original MethodInfo and then look up the already copied method info in the new type.

Non-trivial.

Same sort of thing would be required for fields but simpler as we can do the fields first and then the methods that reference them.

double-beep
  • 5,031
  • 17
  • 33
  • 41