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.
GetILAsByteArray
, there does not appear to be any reference to the token. – LandholderIL
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 – LandholderAssemblyBuilderAccess.RunAndSave
so you can save your created assembly to the disk. Next you can use Peverify.exe to verify that everything is ok. YourFib
function will not run because it contains a metadata token in the call instruction. – EnsphereIL_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. – MisanthropyMemberInfo
in your program, and the metadata token out of theMemberInfo
. – MisanthropyILGenerator.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 thexxxInfo
orxxxBuilder
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. – EnsphereMethodBuilder
objectmtbl
. Otherwise get theMethodInfo
for the method and emit a call with it. – Misanthropy