为什么调用 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
我不得不对你的代码进行大量修改才能编译和 运行。一旦我这样做了,它 运行 完美,所以我不确定我的按摩的哪一部分解决了你遇到的问题。我所做的更改:
- 当您定义
tb
(通过调用 DefineType
)时,您需要确保它继承自 baseCrudController
。既然你声明 baseCrudController
later,我不相信你这样做了。不过不好说,因为这段代码不在你的问题中
- 在您对
tb.DefineMethod
的调用中,C# 中的字符串需要双引号,而不是单引号
- 在同一个调用中,您需要
typeof(Foo), new[] { typeof(int) }
而不是 typeof(Foo), typeof(int)
,因为您需要传递一个参数类型数组
- 您需要使用
typeof(HttpGetAttribute)
,而不是 typeof(HttpGet)
HttpGetAttribute
有多个构造函数,你不能依赖它们返回的顺序。明确指定你想要无参数构造函数
- 您不需要
nops
,也不需要 call
之后的 stloc
/ldloc
。这些仅添加到调试版本中以帮助调试器
- 事实上,由于您还没有声明本地插槽
0
,因此 stloc.0
和 ldloc.0
无效。当您使用 System.Reflection.Emit
时,如果您需要将值存储到本地槽中,请使用 LocalBuilder
并且不要通过数字显式寻址槽。
- 当你调用基本方法时,它的名字是
"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 });
我正在运行时生成 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
我不得不对你的代码进行大量修改才能编译和 运行。一旦我这样做了,它 运行 完美,所以我不确定我的按摩的哪一部分解决了你遇到的问题。我所做的更改:
- 当您定义
tb
(通过调用DefineType
)时,您需要确保它继承自baseCrudController
。既然你声明baseCrudController
later,我不相信你这样做了。不过不好说,因为这段代码不在你的问题中 - 在您对
tb.DefineMethod
的调用中,C# 中的字符串需要双引号,而不是单引号 - 在同一个调用中,您需要
typeof(Foo), new[] { typeof(int) }
而不是typeof(Foo), typeof(int)
,因为您需要传递一个参数类型数组 - 您需要使用
typeof(HttpGetAttribute)
,而不是typeof(HttpGet)
HttpGetAttribute
有多个构造函数,你不能依赖它们返回的顺序。明确指定你想要无参数构造函数- 您不需要
nops
,也不需要call
之后的stloc
/ldloc
。这些仅添加到调试版本中以帮助调试器 - 事实上,由于您还没有声明本地插槽
0
,因此stloc.0
和ldloc.0
无效。当您使用System.Reflection.Emit
时,如果您需要将值存储到本地槽中,请使用LocalBuilder
并且不要通过数字显式寻址槽。 - 当你调用基本方法时,它的名字是
"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 });