1

我正在尝试构造一个泛型类型的实例并在该实例上调用一个方法。然后返回方法的结果。

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: 'Bad class token.'”异常。

GenericType 类看起来像这样

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

1 回答 1

3

您混淆了生成程序和生成程序之间的界限。

具体来说,您的生成程序在运行时构造一个对象的实例(一个MethodInfo实例),然后尝试生成一个使用该实例的程序 - 它不能因为该实例在生成的程序中不存在,它存在在生成程序的内存中。

您必须MethodInfo在生成的程序中构造实例 - 您必须编写 Emit 代码来生成构造MethodInfo实例的 IL。

您尝试执行的操作与执行以下操作一样有意义:

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或某种形式。

回到原点,您收到错误“Bad class token”的原因是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 );
}
于 2017-07-27T05:23:27.717 回答