Я пытаюсь создать копию метода во время выполнения, используя отражение.
У меня есть следующий код.
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);
}
Последняя строка выдает исключение с сообщением:
Cреда CLR обнаружила недопустимую программу.
Есть ли другой способ сделать это? Я бы предпочел иметь возможность получить дерево синтаксического анализа метода вместо прямого использования 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:
Ответ Роба помогает решить вышеуказанную проблему. Однако при использовании чуть более сложного метода Fib()
(например, закомментированного метода Фибоначчи) программа вылетает со следующим сообщением.
Индекс не найден. (Исключение из 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;
}
Я могу создать динамический метод, используя подход, предложенный в ответе на соответствующий вопрос: Ссылка на коллекцию из метода, созданного на языке IL. Однако при попытке сделать то же самое здесь это не удается.
GetILAsByteArray
, не было никаких ссылок на токен. - person Rob   schedule 22.01.2016IL
совершенно верна, поэтому ответ о том, что IL был искажен, неверен, и я не хочу вводить в заблуждение будущих читателей. Если хотите, я могу поместить код в отрывок pastebin в комментариях, но я не думаю, что он подходит в качестве ответа. - person Rob   schedule 22.01.2016AssemblyBuilderAccess.RunAndSave
, чтобы вы могли сохранить созданную сборку на диск. Затем вы можете использовать Peverify.exe чтобы убедиться, что все в порядке. Ваша функцияFib
не будет работать, так как она содержит токен метаданных в инструкции вызова. - person thehennyy   schedule 22.01.2016IL_0010: /* 28 | (06)000002 */ call int32 ConsoleApplication1.Program::Fib(int32)
. Вам нужно найти 06000002, спросить сборку, что это такое, и заменить его своим собственным значением маркера метаданных в контексте вашего нового метода. - person elchido   schedule 22.01.2016MemberInfo
в своей программе, а токен метаданных изMemberInfo
. - person elchido   schedule 22.01.2016ILGenerator.Emit()
никогда не принимает токен метаданных в качестве аргумента, потому что эти токены должны генерироваться самой динамической сборкой, токен метаданных действителен только в рамках своего модуля. Вместо этого вы всегда будете передавать экземплярxxxInfo
илиxxxBuilder
. Чтобы было немного понятнее, в большинстве случаев класс xxxBuilder является подклассом соответствующего ему класса xxxInfo, поэтому его можно использовать. Имейте это в виду, когда ваш испускаемый код становится более сложным и включает в себя другие динамически сгенерированные члены. - person thehennyy   schedule 25.01.2016MethodBuilder
объектmtbl
. В противном случае получитеMethodInfo
для метода и вызовите его. - person elchido   schedule 25.01.2016