问题
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