Как получить байтовый массив IL из DynamicMethod?
В качестве новшества я пытаюсь увидеть, насколько отличается IL от легковесного кода, сгенерированного во время выполнения, от кода, сгенерированного компилятором VS, поскольку я заметил, что код VS имеет тенденцию работать с другим профилем производительности для таких вещей, как слепки.
Поэтому я написал следующий код:
Func<object,string> vs = x=>(string)x;
Expression<Func<object,string>> exp = x=>(string)x;
var compiled = exp.Compile();
Array.ForEach(vs.Method.GetMethodBody().GetILAsByteArray(),Console.WriteLine);
Array.ForEach(compiled.Method.GetMethodBody().GetILAsByteArray(),Console.WriteLine);
К сожалению, это вызывает исключение, поскольку GetMethodBody, по-видимому, является недопустимой операцией над кодом, сгенерированным деревьями выражений. Как я могу библиотечным способом (т.е. не с помощью внешнего инструмента, если у инструмента нет API) посмотреть на код, сгенерированный кодом с использованием облегченного codegen?
Редактировать: ошибка возникает в строке 5, compiled.Method.GetMethodBody() выдает исключение.
Edit2: кто-нибудь знает, как восстановить локальные переменные, объявленные в методе? Или нет способа получить GetVariables?
5 ответов
Да, не работает, метод генерируется Reflection.Emit. IL хранится в ILGenerator для MethodBuilder. Вы можете выкопать это, но вы должны быть довольно отчаянным. Отражение необходимо, чтобы добраться до внутренних и частных членов. Это работает на.NET 3.5SP1:
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
...
var mtype = compiled.Method.GetType();
var fiOwner = mtype.GetField("m_owner", BindingFlags.Instance | BindingFlags.NonPublic);
var dynMethod = fiOwner.GetValue(compiled.Method) as DynamicMethod;
var ilgen = dynMethod.GetILGenerator();
var fiBytes = ilgen.GetType().GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic);
var fiLength = ilgen.GetType().GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic);
byte[] il = fiBytes.GetValue(ilgen) as byte[];
int cnt = (int)fiLength.GetValue(ilgen);
// Dump <cnt> bytes from <il>
//...
В.NET 4.0 вам придется использовать ilgen.GetType().BaseType.GetField(...), потому что был изменен генератор IL, DynamicILGenerator, производный от ILGenerator.
ILReader слышать должно работать.
ILVisualizer 2010 Solution
http://blogs.msdn.com/b/haibo_luo/archive/2010/04/19/9998595.aspx
Текущие решения здесь не очень хорошо отражают текущую ситуацию в.NET 4. Вы можете использовать либо DynamicILInfo
или же ILGenerator
создать динамический метод, но решения, перечисленные здесь, не работают с DynamicILInfo
динамические методы вообще.
Используете ли вы DynamicILInfo
метод генерации IL или ILGenerator
метод, байт-код IL заканчивается в DynamicMethod.m_resolver.m_code
, Вам не нужно проверять оба метода, и это менее сложное решение.
Это версия, которую вы должны использовать:
public static byte[] GetILBytes(DynamicMethod dynamicMethod)
{
var resolver = typeof(DynamicMethod).GetField("m_resolver", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(dynamicMethod);
if (resolver == null) throw new ArgumentException("The dynamic method's IL has not been finalized.");
return (byte[])resolver.GetType().GetField("m_code", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(resolver);
}
Посмотрите этот ответ для получения дополнительных вспомогательных методов и решения проблемы с разрешением токена DynamicMethod.
Основываясь на работе Ханса Пассанта, я смог немного глубже понять, что существует метод, который вы должны вызвать, называемый BakeByteArray
так что следующие работы::
var dynMethod = fiOwner.GetValue(compiled.Method) as DynamicMethod;
var ilgen =dynamicMethod.GetILGenerator();
byte[] il = ilgen.GetType().GetMethod("BakeByteArray", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(ilgen, null) as byte[];
Это, конечно, помогает, но у меня все еще нет возможности решить VariableInfo's
только пока что кое-что, что помогло бы в моей работе.
Я только что объединил решения @Hans Passant и @jnm2 в метод расширения и добавил полезные комментарии:
public static byte[] GetIlAsByteArray(this DynamicMethod dynMethod)
{
// First we try to retrieve the value of "m_resolver" field,
// which will always be null unless the dynamic method is completed
// by either calling 'dynMethod.CreateDelegate()' or 'dynMethod.Invoke()' function.
// Source: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.dynamicmethod.getilgenerator
// (Remarks section)
// Note that the dynamic method object does not know when it is ready for use
// since there is not API which indicates that IL generation has completed.
// Source: https://referencesource.microsoft.com/#mscorlib/system/reflection/emit/dynamicmethod.cs,7fc135a2ceea0854,references
// (Comment lines)
// So, if the dynamic method is not completed, we will retrieve the "m_ILStream" field instead.
// The only difference I notice between "m_resolver" and "m_ILStream" fields is that the IL bytes in "m_ILStream"
// will have trailing zeros / null bytes depending on the amount of unused bytes in this buffer.
// ( The buffer size for "m_ILStream" is allocated by a call to 'dynMethod.GetILGenerator(streamSize)' function. )
BindingFlags bindingFlags = bindingFlags.Instance | bindingFlags.NonPublic;
object fiResolver = typeof(DynamicMethod).GetField("m_resolver", bindingFlags).GetValue(dynMethod);
if (fiResolver == null)
{
ILGenerator ilGen = dynMethod.GetILGenerator();
FieldInfo fiIlStream = null;
// Conditional for .NET 4.x because DynamicILGenerator class derived from ILGenerator.
// Source: https://stackoverflow.com/a/4147132/1248295
if (Environment.Version.Major >= 4)
{
fiIlStream = ilGen.GetType().BaseType.GetField("m_ILStream", bindingFlags);
}
else // This worked on .NET 3.5
{
fiIlStream = ilGen.GetType().GetField("m_ILStream", bindingFlags);
}
return fiIlStream.GetValue(ilGen) as byte[];
}
else
{
return (byte[])(fiResolver.GetType().GetField("m_code", bindingFlags).GetValue(fiResolver));
}
}