Как получить байтовый массив 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));

    }

}
Другие вопросы по тегам