2016-01-21 4 views
5

Я пытаюсь создать копию метода во время выполнения с использованием отражения.Создайте копию метода от 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 напрямую.

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

EDIT 2: ответ

Роба помогает решить вышеуказанную проблему. Однако при использовании метода Fib(), который немного сложнее (например, прокомментированный метод Фибоначчи), программа выходит из строя со следующим сообщением.

Индекс не найден. (Исключение из HRESULT: 0x80131124)

EDIT 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. Однако, пытаясь сделать то же самое здесь, он терпит неудачу.

+0

Вы не можете скопировать метод, подобный этому, когда инструкция в нем каким-то образом использует маркер метаданных. – thehennyy

+0

@thehennyy Можете ли вы предложить исправить? –

+0

@thehennyy При изучении IL, взятого из «GetILAsByteArray», похоже, нет никакой ссылки на токен. – Rob

ответ

2

Мне удалось осуществить реконструкцию, основанную на очень полезной дискуссии в комментариях. Он не учитывает все возможные сценарии, но очень хорошо иллюстрирует решение.

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