为什么调用 IL 生成的方法会抛出 InvalidProgramException?

Why does call of IL generated method throw InvalidProgramException?

我正在运行时生成 Web API 控制器 (.Net Core 3.0)。我拥有的是一个通用的基本控制器,我从中继承了生成的控制器,并且在设置 http 操作时,我想从基本 class 和 return 的值中调用一个方法。当我调用 /api/Foo/1 时,会引发 InvalidProgramException。

编辑:我简化了代码并添加了一个 运行 示例(参见 Working Example project)。

我想达到的目标:

public class FooController : GenericBaseController<Foo, int> {

    [HttpGet]
    public Foo Get(int id){
        return base.DoGet(id);
    }
}

基本控制器的外观:

[Route("api/[controller]")]
[ApiController]
public abstract class GenericBaseController<T, TId> : ControllerBase {

    protected T DoGet(TId id)
    { 
        return default;
    }
}

设置方法的类型生成器代码如下所示:

        MethodBuilder builder = tb.DefineMethod("Get", MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Final, modelType, new Type[] {typeof(int)});

        builder.DefineParameter(1, ParameterAttributes.None, "id");

        ConstructorInfo httpCtorInfo = typeof(HttpGetAttribute).GetConstructors().First();
        CustomAttributeBuilder httpAttrBuilder = new CustomAttributeBuilder(httpCtorInfo, new object[0]);
        builder.SetCustomAttribute(httpAttrBuilder);

        ILGenerator emitter = builder.GetILGenerator();

        emitter.Emit(OpCodes.Nop);

        emitter.Emit(OpCodes.Ldarg_0); 
        emitter.Emit(OpCodes.Ldarg_1); 

        MethodInfo baseMethod = baseCrudController.GetMethod("DoGet", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
        emitter.Emit(OpCodes.Call, baseMethod);

        emitter.Emit(OpCodes.Stloc_0);
        emitter.Emit(OpCodes.Br_S, 0011);
        emitter.Emit(OpCodes.Ldloc_0);

        emitter.Emit(OpCodes.Ret);

我尝试使用此处找到的 ilreader 从我获得的 OpCodes 中重建https://www.codeproject.com/Articles/14058/Parsing-the-IL-of-a-Method-Body

0000 : nop 
0001 : ldarg.0 
0002 : ldarg.1 
0003 : call instance FooProject.Controllers.GenericBaseController`1[System.Int32]::DoGet() 
0008 : stloc.0 
0009 : br.s 0011 
0011 : ldloc.0 
0012 : ret

我不得不对你的代码进行大量修改才能编译和 运行。一旦我这样做了,它 运行 完美,所以我不确定我的按摩的哪一部分解决了你遇到的问题。我所做的更改:

  1. 当您定义 tb(通过调用 DefineType)时,您需要确保它继承自 baseCrudController。既然你声明 baseCrudController later,我不相信你这样做了。不过不好说,因为这段代码不在你的问题中
  2. 在您对 tb.DefineMethod 的调用中,C# 中的字符串需要双引号,而不是单引号
  3. 在同一个调用中,您需要 typeof(Foo), new[] { typeof(int) } 而不是 typeof(Foo), typeof(int),因为您需要传递一个参数类型数组
  4. 您需要使用 typeof(HttpGetAttribute),而不是 typeof(HttpGet)
  5. HttpGetAttribute 有多个构造函数,你不能依赖它们返回的顺序。明确指定你想要无参数构造函数
  6. 您不需要 nops,也不需要 call 之后的 stloc/ldloc。这些仅添加到调试版本中以帮助调试器
  7. 事实上,由于您还没有声明本地插槽 0,因此 stloc.0ldloc.0 无效。当您使用 System.Reflection.Emit 时,如果您需要将值存储到本地槽中,请使用 LocalBuilder 并且不要通过数字显式寻址槽。
  8. 当你调用基本方法时,它的名字是"DoGet"而不是"GetInBase"

进行所有这些更改后,您将得到有效的结果:

var assemblyName = new AssemblyName("Test");
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Test");
Type baseCrudController = typeof(GenericBaseController<,>).MakeGenericType(typeof(Foo), typeof(int));
var tb = moduleBuilder.DefineType("FooController", TypeAttributes.Public, baseCrudController);

MethodBuilder builder = tb.DefineMethod("Get", MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Final, typeof(Foo), new[] { typeof(int) });

ConstructorInfo httpCtorInfo = typeof(HttpGetAttribute).GetConstructor(Type.EmptyTypes);
CustomAttributeBuilder httpAttrBuilder = new CustomAttributeBuilder(httpCtorInfo, new object[0]);
builder.SetCustomAttribute(httpAttrBuilder);

ILGenerator emitter = builder.GetILGenerator();

emitter.Emit(OpCodes.Ldarg_0);
emitter.Emit(OpCodes.Ldarg_1);

MethodInfo baseTypeMethod = baseCrudController.GetMethod("DoGet", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
emitter.Emit(OpCodes.Call, baseTypeMethod);

emitter.Emit(OpCodes.Ret);

// Test
var resultType = tb.CreateType();
var instance = Activator.CreateInstance(resultType);
var result = resultType.GetMethod("Get").Invoke(instance, new object[] { 3 });