从 IL 创建方法的副本
Create a copy of method from 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 detected an invalid program.
还有其他方法吗?我更希望能够获取方法的解析树而不是直接使用 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 方法),程序会崩溃并显示以下消息。
Index not found. (Exception from 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;
}
我可以使用相关问题的答案中建议的方法构建动态方法:。然而,当在这里尝试做同样的事情时,它失败了。
基于评论中非常有帮助的讨论,我设法实现了重建。它没有解决所有可能的情况,但很好地说明了解决方案。
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 的有用解决方案的问题在于它对传递给函数的信息使用了 ResolveMethod。这意味着它将把克隆的实例转换为原始类型(这是不允许的,但我们在 IL 中!),然后调用原始方法。例如如果我在原始 class、TestClass
中有两个方法,称为 SimpleMethod
和 MethodCallingSimpleMethod
,那么复制的类型将执行如下操作:
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);
}
}
要完全实现这一点,我们需要找到方法之间的依赖关系。按正确的顺序复制它们,然后使用元令牌解析到原始的 MethodInfo,然后在新类型中查找已复制的方法信息。
不平凡。
字段也需要同样的事情,但更简单,因为我们可以先处理字段,然后再处理引用它们的方法。
我正在尝试使用反射在运行时创建一个方法的副本。
我有以下代码。
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 detected an invalid program.
还有其他方法吗?我更希望能够获取方法的解析树而不是直接使用 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 方法),程序会崩溃并显示以下消息。
Index not found. (Exception from 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;
}
我可以使用相关问题的答案中建议的方法构建动态方法:
基于评论中非常有帮助的讨论,我设法实现了重建。它没有解决所有可能的情况,但很好地说明了解决方案。
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 的有用解决方案的问题在于它对传递给函数的信息使用了 ResolveMethod。这意味着它将把克隆的实例转换为原始类型(这是不允许的,但我们在 IL 中!),然后调用原始方法。例如如果我在原始 class、TestClass
中有两个方法,称为 SimpleMethod
和 MethodCallingSimpleMethod
,那么复制的类型将执行如下操作:
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);
}
}
要完全实现这一点,我们需要找到方法之间的依赖关系。按正确的顺序复制它们,然后使用元令牌解析到原始的 MethodInfo,然后在新类型中查找已复制的方法信息。
不平凡。
字段也需要同样的事情,但更简单,因为我们可以先处理字段,然后再处理引用它们的方法。