2016-01-21 25 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); 
} 

最後の行には、メッセージと例外をスロー:

共通言語ランタイムは、無効なプログラムを検出しました。

別の方法がありますか? 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

ロブの答えは、上記の問題を解決するのに役立ちます。しかしながら、少し複雑な方法(例えば、コメントされたフィボナッチ法)を用いる場合、プログラムは以下のメッセージでクラッシュする。

インデックスが見つかりません。 (HRESULTからの例外:0x80131124)

EDIT 3

Iのコメントから、いくつかの提案を試みたが、メタデータトークンは、動的アセンブリ内に配置することができません。

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 'GetILAsByteArray'から取得したILを調べるとき、トークンへの参照がないようです。 – 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 { } 
    } 
}