Приведение объекта к определенному классу в IL? - PullRequest
4 голосов
/ 13 октября 2009

Я обнаружил причину, по которой я получаю «Операция может дестабилизировать среду выполнения» в производимом мной DynamicMethod, и, хотя я легко это исправил, у меня возник простой вопрос:

  • Как преобразовать ссылку на объект типа «Объект» в определенный тип, чтобы я мог вызывать методы этого типа для ссылки на объект?

Ниже приведен пример программы. При запуске этого приложения происходит сбой с исключением «Операция может дестабилизировать среду выполнения» при компиляции метода.

Проблема решается простым изменением типа объявленной переменной типа TestClass вместо Object, но я все еще хочу знать, как я могу привести ссылку на соответствующий тип в коде.

В коде я пометил строку звездочками. Что я могу выдать в тот момент кода, который вместо этого превратит ссылку Object в стеке в ссылку TestClass, чтобы вызов метода прошел?

Обратите внимание, что я знаю, что это вызов метода, который создает проблему, если я закомментирую строки вообще, не имеет значения, какой тип переменной, метод скомпилирован и выполняется нормально.

Вот код.

using System;
using System.Reflection.Emit;

namespace ConsoleApplication9
{
    public class TestClass
    {
        public void TestMethod() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Type type = typeof(TestClass);
            DynamicMethod method = new DynamicMethod("", typeof(Object), null);
            ILGenerator il = method.GetILGenerator();
            LocalBuilder variable = il.DeclareLocal(typeof(Object));

            // Construct object
            il.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0]));
            il.Emit(OpCodes.Stloc, variable);

            // Call Test method
            il.Emit(OpCodes.Ldloc, variable);
            // ***************************************** what do I do here?
            il.Emit(OpCodes.Call, type.GetMethod("TestMethod"));

            // Return object
            il.Emit(OpCodes.Ldloc, variable);
            il.Emit(OpCodes.Ret);

            // Create and call delegate
            Func<Object> fn = (Func<Object>)method.CreateDelegate(
                typeof(Func<Object>));
            Object instance = fn();
        }
    }
}

1 Ответ

11 голосов
/ 14 октября 2009

Краткий ответ:

// Call Test method
il.Emit(OpCodes.Ldloc, variable);
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Call, type.GetMethod("TestMethod"));

Как это сделать? Ну, метод, который я использовал, был Отражатель . Во-первых, напишите метод, который делает то, что вы хотите сделать. Я придумал следующее:

private static object PrecompiledTest()
{
    object variable = new TestClass();
    ((TestClass) variable).TestMethod();
    return variable;
}

Теперь, скомпилируйте это, откройте Reflector и откройте вашу сборку. Перейдите к своей функции и посмотрите на MSIL. Вышеприведенная функция декомпилируется в следующее:

.method private hidebysig static object PrecompiledTest() cil managed
{
    .maxstack 1
    .locals init (
        [0] object variable,
        [1] object CS$1$0000)
    L_0000: nop 
    L_0001: newobj instance void EmitTest.TestClass::.ctor()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: castclass EmitTest.TestClass
    L_000d: callvirt instance void EmitTest.TestClass::TestMethod()
    L_0012: nop 
    L_0013: ldloc.0 
    L_0014: stloc.1 
    L_0015: br.s L_0017
    L_0017: ldloc.1 
    L_0018: ret 
}

Выше используется callvirt вместо call. Я не очень хорошо разбираюсь в IL, поэтому я не уверен в разнице, но call работает в вашем примере. И последнее, пока мы говорим о Reflector. Вы можете использовать надстройку ReflectionEmitLanguage , чтобы сгенерировать для вас свой код Emit. Этот плагин генерирует для вас следующий код:

public MethodBuilder BuildMethodPrecompiledTest(TypeBuilder type)
{
    // Declaring method builder
    // Method attributes
    System.Reflection.MethodAttributes methodAttributes = 
          System.Reflection.MethodAttributes.Private
        | System.Reflection.MethodAttributes.HideBySig
        | System.Reflection.MethodAttributes.Static;
    MethodBuilder method = type.DefineMethod("PrecompiledTest", methodAttributes);
    // Preparing Reflection instances
    ConstructorInfo ctor1 = typeof(TestClass).GetConstructor(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 
        null, 
        new Type[]{
            }, 
        null
        );
    MethodInfo method2 = typeof(TestClass).GetMethod(
        "TestMethod", 
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 
        null, 
        new Type[]{
            }, 
        null
        );
    // Setting return type
    method.SetReturnType(typeof(Object));
    // Adding parameters
    ILGenerator gen =  method.GetILGenerator();
    // Preparing locals
    LocalBuilder variable =  gen.DeclareLocal(typeof(Object));
    LocalBuilder CS$1$0000 =  gen.DeclareLocal(typeof(Object));
    // Preparing labels
    Label label23 =  gen.DefineLabel();
    // Writing body
    gen.Emit(OpCodes.Nop);
    gen.Emit(OpCodes.Newobj,ctor1);
    gen.Emit(OpCodes.Stloc_0);
    gen.Emit(OpCodes.Ldloc_0);
    gen.Emit(OpCodes.Castclass,TestClass);
    gen.Emit(OpCodes.Callvirt,method2);
    gen.Emit(OpCodes.Nop);
    gen.Emit(OpCodes.Ldloc_0);
    gen.Emit(OpCodes.Stloc_1);
    gen.Emit(OpCodes.Br_S,label23);
    gen.MarkLabel(label23);
    gen.Emit(OpCodes.Ldloc_1);
    gen.Emit(OpCodes.Ret);
    // finished
    return method;
}
...