Create a copy of the method from IL

I am trying to create a copy of a method at 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 the message:

The Common Language runtime detected an invalid program.

Is there any other way to do this? I would rather be able to get the method parsing tree instead of using IL directly.

EDIT 1 :

I am testing 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 next line.

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

EDIT 2 :

Rob's answer helps solve the above problem. However, when using the Fib() method, which is a bit more complicated (for example, the commented Fibonacci method), the program crashes with the following message.

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

EDIT 3 :

I tried several suggestions from comments, but the metadata token cannot be located in 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 does not work either.

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

I can fix recursive function calls by changing the metadata token as follows (I understand that this will not work in all cases, but I'm trying to get it to work somehow).

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

I can build a dynamic method using the approach proposed in the answer to the corresponding question: Link to the collection from the constructed IL method . However, trying to do the same here, he fails.

+5
source share
2 answers

I managed to implement a reconstruction based on a very useful discussion in the comments. It does not take into account all possible scenarios, but it 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 { } } } 
+2
source

The problem with a useful solution from Igor is that he uses ResolveMethod for the information passed to the function. This means that it will distinguish the cloned instance with the original type (which should not be allowed, but we are in IL!), And then call the original method. for example, if I have two methods in my source 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 fully implement this, we need to find the dependencies between the methods. Copy them in the correct order, and then use the meta token to enable the original MethodInfo, and then view the already copied method information in the new type.

Nontrivial.

The same will be required for fields, but easier, since we can first create fields, and then methods that reference them.

0
source

Source: https://habr.com/ru/post/1241177/


All Articles