ILGenerator call instance method with parameter

不想你离开。 提交于 2019-12-23 03:18:23

问题


I am trying to construct an instance of a generic type and call a method on that instance. Then return the result of the method.

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);

I keep getting a 'System.BadImageFormatException: 'Bad class token.'' exception.

GenericType class looks like this

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

回答1:


You're confusing the boundary between the generated program, and the generating program.

Specifically, your generating program, when run, constructs an instance of an object (a MethodInfo instance) and then attempts to generate a program that uses that instance - which it can't because that instance doesn't exist in the generated program, it exists in the memory of the generating program.

You have to construct the instance of MethodInfo inside your generated program - you have to write the Emit code to generate the IL that constructs the MethodInfo instance.

What you're trying to do makes about as much sense as doing the following:

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);

Thats problem number 1.

Problem number 2 is that you're using the wrong opcode when trying to provide an argument to a method - Ldobj doesn't do what you think it does.

Instead of using Ldobj, you'll have to load the reference by whatever means you fix your generation code to create the internal methodInfo. It'll probably be local, so you'll probably end up using Ldloc or some form thereof.

To come full circle, the reason why you're getting the error "Bad class token" is that the value that is supposed to follow Ldobj in the compiled IL is supposed to be a class metadata token. What you provided was not a class token, hence the error.

As a demonstration, below is a complete program that emulates what you're attempting to do.

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 );
}


来源:https://stackoverflow.com/questions/45341097/ilgenerator-call-instance-method-with-parameter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!