Метод экземпляра вызова ILGenerator с параметром

Я пытаюсь создать экземпляр универсального типа и вызвать метод для этого экземпляра. Затем верните результат метода.

MethodInfo methodInfo = ...;
...
var genericType = typeof(GenericType<>).MakeGenericType(typeof(TOutput));
il.Emit(OpCodes.Newobj, genericType.GetConstructor(Type.EmptyTypes));
il.Emit(OpCodes.Ldobj, methodInfo);
il.Emit(OpCodes.Callvirt, genericeClientHelper.GetMethod("MethodName", new Type[] { typeof(MethodInfo) }));
il.Emit(OpCodes.Ret);

Я продолжаю получать исключение «System.BadImageFormatException: «Плохой токен класса».

Класс GenericType выглядит так

public class GenericType<T>
{
    public T MethodName(MethodInfo methodInfo)
    {
        ...
    }
}

person Mike    schedule 27.07.2017    source источник
comment
Можете ли вы показать нам немного больше кода? Мол, как вы конструируете тип, как вы определяете метод? Ваш текущий код выглядит нормально (хотя genericeClientHelper.GetMethod, вероятно, должно быть genericType.GetMethod, я думаю.   -  person Rob    schedule 27.07.2017


Ответы (1)


Вы путаете границу между генерируемой программой и генерирующей программой.

В частности, ваша генерирующая программа при запуске конструирует экземпляр объекта (экземпляр MethodInfo), а затем пытается сгенерировать программу, которая использует этот экземпляр, но не может, потому что этот экземпляр не существует в сгенерированной программе. существует в памяти генерирующей программы.

Вы должны создать экземпляр MethodInfo внутри сгенерированной программы — вы должны написать код Emit для создания IL, который создает экземпляр MethodInfo.

То, что вы пытаетесь сделать, имеет такой же смысл, как и следующее:

Person person = new Person( "Antiduh", "United States" );

var genericType = typeof(GenericType<>).MakeGenericType(typeof(TOutput));
il.Emit(OpCodes.Newobj, genericType.GetConstructor(Type.EmptyTypes));

// This doesn't make sense. The object referred to by 
// my `person` variable doesn't exist in the generated program.
il.Emit(OpCodes.Ldobj, person);

il.Emit(OpCodes.Callvirt, genericeClientHelper.GetMethod("MethodName", new Type[] { typeof(MethodInfo) }));
il.Emit(OpCodes.Ret);

Это проблема номер 1.

Проблема номер 2 заключается в том, что вы используете неправильный код операции при попытке предоставить аргумент методу - Ldobj не делает того, что вы думаете.

Вместо использования Ldobj вам придется загрузить ссылку любым способом, который вы исправите в коде генерации, чтобы создать внутренний файл methodInfo. Это, вероятно, будет локальным, поэтому вы, вероятно, в конечном итоге будете использовать Ldloc или какую-то его форму.

Чтобы завершить круг, причина, по которой вы получаете сообщение об ошибке «Плохой токен класса», заключается в том, что значение, которое должно следовать за Ldobj в скомпилированном IL, должно быть токеном метаданных класса. То, что вы предоставили, не было токеном класса, отсюда и ошибка.

В качестве демонстрации ниже приведена полная программа, которая эмулирует то, что вы пытаетесь сделать.

private static void BuildAssembly()
{
    AssemblyName assemblyName;
    AssemblyBuilder assmBuilder;
    ModuleBuilder modBuilder;

    assemblyName = new AssemblyName( "Generated" );

    // Note the save directory is the directory containing this project's solution file.
    assmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
        assemblyName,
        AssemblyBuilderAccess.RunAndSave,
        Assembly.GetExecutingAssembly().Location + @"\..\..\..\.."
    );

    modBuilder = assmBuilder.DefineDynamicModule(
        assemblyName.Name,
        assemblyName.Name + ".dll",
        true
    );

    /*
     * public class GenericsDemo {
     * }
     */
    TypeBuilder typeBuilder = modBuilder.DefineType(
        "Generated.GenericsDemo",
            TypeAttributes.Public
    );

    BuildCallListMethod( typeBuilder );

    typeBuilder.CreateType();

    assmBuilder.Save( assemblyName.Name + ".dll" );
}

private static void BuildCallListMethod( TypeBuilder typeBuilder )
{
    // public void CallList() {
    //     List<object> list = new List<object>();
    //     object thing = new object();
    //     list.Add(thing);
    // }

    var listOfObject = typeof( List<object> );
    var objType = typeof( object );

    // public void CallList() { 
    var method = typeBuilder.DefineMethod( 
        "CallList", 
        MethodAttributes.Public | MethodAttributes.HideBySig, 
        CallingConventions.HasThis 
    );

    var gen = method.GetILGenerator();

    // List<int> list;
    var listLocal = gen.DeclareLocal( listOfObject );
    listLocal.SetLocalSymInfo( "list" );

    // object thing;
    var thingLocal = gen.DeclareLocal( objType );
    thingLocal.SetLocalSymInfo( "thing" );

    // list = new List<object>();
    gen.Emit( OpCodes.Newobj, listOfObject.GetConstructor( Type.EmptyTypes ) );
    gen.Emit( OpCodes.Stloc_0 );

    // thing = new object();
    gen.Emit( OpCodes.Newobj, objType.GetConstructor( Type.EmptyTypes ) );
    gen.Emit( OpCodes.Stloc_1 );

    // list.Add( thing );
    gen.Emit( OpCodes.Ldloc_0 ); // loads `list`.
    gen.Emit( OpCodes.Ldloc_1 ); // loads `thing`.
    gen.EmitCall( OpCodes.Callvirt, listOfObject.GetMethod( "Add" ), null );

    gen.Emit( OpCodes.Ret );
}
person antiduh    schedule 27.07.2017
comment
Спасибо за ответ. Я знал, что проблема была в том, что MethodInfo находился в неправильном слое. Я надеялся, что есть способ отправить его в сгенерированный метод таким образом, чтобы он работал, не разрывая нужные мне данные по частям. - person Mike; 27.07.2017