Создать DynamicMethod из инструкции Action<T>
Я играю с DynamicMethod и стремлюсь сделать следующее:
У меня есть действие, из которого я получаю код IL в виде байтов, используя GetILAsByteArray()
, Из этого байта я хотел бы создать динамический метод и выполнить. Вот пример того, что я пытаюсь сделать:
class Program
{
static void Main(string[] args)
{
//Create action and execute
Action<string> myAction = s =>
{
Console.WriteLine("Hello " + s);
};
myAction("World");
//Get IL bytes
byte[] ilBytes = myAction.GetMethodInfo().GetMethodBody().GetILAsByteArray();
DynamicMethod dynamicCallback = new DynamicMethod("myAction", typeof(void), new Type[] { typeof(string) });
DynamicILInfo dynamicIlInfo = dynamicCallback.GetDynamicILInfo();
dynamicIlInfo.SetCode(ilBytes, 100);
dynamicCallback.Invoke(null, new object[] { "World" });
}
}
При вызове dynamicCallback.Invoke(null, new object[] { "World" })
мы получаем "Исключение:" System.BadImageFormatException "в mscorlib.dll".
Я понятия не имею, что мне следует использовать в качестве второго аргумента для SetCode()
, что следует использовать как 'maxStackSize'? Как я могу установить то же значение, что и для начального действия? Но я полагаю, что это не причина для исключения.
Как правильно создать динамический метод из байтов IL?
Решение
Здесь я хотел бы обобщить полное решение, предоставленное Dudi Keleti:
static void Main(string[] args)
{
Action<string> myAction = s =>
{
Console.WriteLine("Hello " + s);
};
MethodInfo method = myAction.GetMethodInfo();
object target = myAction.Target;
DynamicMethod dm = new DynamicMethod(
method.Name,
method.ReturnType,
new[] {method.DeclaringType}.
Concat(method.GetParameters().
Select(pi => pi.ParameterType)).ToArray(),
method.DeclaringType,
skipVisibility: true);
DynamicILInfo ilInfo = dm.GetDynamicILInfo();
var body = method.GetMethodBody();
SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper();
foreach (LocalVariableInfo lvi in body.LocalVariables)
{
sig.AddArgument(lvi.LocalType, lvi.IsPinned);
}
ilInfo.SetLocalSignature(sig.GetSignature());
byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
DynamicMethodHelper.ILInfoGetTokenVisitor visitor = new DynamicMethodHelper.ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);
dm.Invoke(target, new object[] { target, "World" });
Console.ReadLine(); //Just to see the result
}
Примечание: DynamicMethodHelper - это класс, разработанный Haibo Luo и описанный в блоге, но его также можно скачать прямо здесь.
1 ответ
Вы можете сделать это так:
byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);
ILReader
это класс, который делает тяжелую работу за вас. Вы можете скопировать его отсюда.
Пример:
MethodInfo method = ...
DynamicMethod dm = new DynamicMethod(
method.Name,
method.ReturnType,
method.GetParameters.Select(pi => pi.ParameterType).ToArray(),
method.DeclaringType,
skipVisibility: true\fasle - depends of your need);
DynamicILInfo ilInfo = dm.GetDynamicILInfo();
var body = method.GetMethodBody();
SignatureHelper sig = SignatureHelper.GetLocalVarSigHelper();
foreach(LocalVariableInfo lvi in body.LocalVariables)
{
sig.AddArgument(lvi.LocalType, lvi.IsPinned);
}
ilInfo.SetLocalSignature(sig.GetSignature());
byte[] code = body.GetILAsByteArray();
ILReader reader = new ILReader(method);
ILInfoGetTokenVisitor visitor = new ILInfoGetTokenVisitor(ilInfo, code);
reader.Accept(visitor);
ilInfo.SetCode(code, body.MaxStackSize);
Если ваш метод простой (не универсальный и без исключений), thid должен работать.
Если ваш метод является универсальным, вам нужно сделать это для передачи типа владельца в конструктор DynamicMethod:
var owner = method.DeclaringType.MakeGenericType(
method.DeclaringType.GetGenericArguments());
Еще одна вещь, если он все еще не работает, а ваш метод является методом экземпляра, передайте метод instacne в первую ячейку массива paramters DynamicMethod
конструктор.
Обновить
Вы не можете пройти null
Вот dm.Invoke(**null**, new object[] { "World" });
так как myAction
это не статический метод.
myAction
(Action<string>
) на самом деле является методом в новом сгенерированном классе, который содержит этот метод.
Но я проверил, и исключение бросает, даже если я прохожу myAction.Target
или новый экземпляр этого типа. Исключение (CLR определяет недопустимую программу) говорит вам, что IL не совсем корректен. Я не могу сейчас точно сказать, в чем проблема, но если это важно для вас, я могу проверить это на следующей неделе, когда вернусь к работе.
В любом случае, если вы просто хотите увидеть DynamicIlInfo.SetCode в действии, вы можете использовать свой код как есть, но просто измените информацию о методе из этого:
class Program
{
static void Main(string[] args)
{
Action<string> myAction = s =>
{
Console.WriteLine("Hello " + s);
};
MethodInfo method = myAction.GetMethodInfo();
//Rest of your code
}
}
к этому:
class Program
{
static void M(string s)
{
Console.WriteLine("Hello " + s);
}
static void Main(string[] args)
{
MethodInfo method = typeof (Program).GetMethod("M", BindingFlags.Static | BindingFlags.NonPublic);
//Rest of your code
}
}
Обновление 2:
Видимо я вчера очень устал, я не осознал твоей ошибки.
Как я написал в своем первоначальном ответе,
Еще одна вещь, если он все еще не работает, а ваш метод является методом экземпляра, передайте метод instacne в первую ячейку массива paramters
DynamicMethod
конструктор.
Так что вам нужно сделать это:
DynamicMethod dm = new DynamicMethod(
method.Name,
method.ReturnType,
new[] {method.DeclaringType}.
Concat(method.GetParameters().
Select(pi => pi.ParameterType)).ToArray(),
method.DeclaringType,
skipVisibility: true);
И вызвать динамический метод следующим образом:
dm.Invoke(myAction.Target, new object[] { myAction.Target, "World" });
Теперь это отлично работает.