问题描述
我正在为一些内部库找出 Reflection.Emit 并坚持调用作为参数传入的 Func。我的场景测试使用的是图片中用Linqpad传输到IL的代码 我在 Dynamicmethod 中复制 IL 的代码如下
public class ScopeTest
{
public delegate Task WrapScope(Func<Task> value);
public (WrapScope scope,string id) WrapScopeInId()
{
var id = $"wrap~{Guid.NewGuid().ToString().Replace("-",string.Empty)}";
var mi = typeof(Func<Task>).getmethod("Invoke");
var d = new Dynamicmethod(id,typeof(Task),new[] { typeof(Func<Task>) });
var gen = d.GetILGenerator();
var lab = gen.DefineLabel();
gen.Emit(OpCodes.nop);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt,mi);
gen.Emit(OpCodes.Stloc_0);
gen.Emit(OpCodes.Br_S,lab);
gen.MarkLabel(lab);
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Ret);
WrapScope del = (WrapScope)d.CreateDelegate(typeof(WrapScope));
return (del,id);
}
}
代码编译并返回,但是当您调用 WrapScope 委托 del(Func<Task>)
时,它会抛出 system.invalidProgramException:公共语言运行时检测到无效程序。
运行这个 Dynamicmethod 可能有什么问题?
谢谢
解决方法
您的主要问题是您在槽 0 中存储了一个变量,但您从未声明槽 0。如果我们查看您的代码 on SharpLab,我们可以看到 IL 声明了每个槽方法使用:
.locals init (
[0] class [System.Private.CoreLib]System.Threading.Tasks.Task
)
(也就是说我们有 1 个插槽,索引为 0,类型为 Task
)。
您使用 ILGenerator.DeclareLocal
使用 ILGenerator
执行此操作。我们可以使用 ldloc
/stloc
并传入返回的 LocalBuilder
,而不是使用编号的 ldloc.0
/stloc.0
。
var taskLocal = gen.DeclareLocal(typeof(Task));
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt,mi);
gen.Emit(OpCodes.Stloc,taskLocal);
gen.Emit(OpCodes.Br_S,lab);
gen.MarkLabel(lab);
gen.Emit(OpCodes.Ldloc,taskLocal);
gen.Emit(OpCodes.Ret);
这一切都很好,但它包含了很多不必要的说明。 Linqpad 在 Debug 中为您提供编译器的输出,这会发出许多不必要的 NOP 等。您会看到 Release 模式下的 SharpLab 不显示这些,我们可以简单地删除它们。 br.s
也无关紧要,因为它会无条件跳转到下一条指令,因此我们也可以将其删除:
var taskLocal = gen.DeclareLocal(typeof(Task));
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt,taskLocal);
gen.Emit(OpCodes.Ldloc,taskLocal);
gen.Emit(OpCodes.Ret);
现在 stloc
/ldloc
看起来毫无意义:我们从堆栈中取出一个变量,将其移动到本地,然后立即将其从本地复制回并按顺序复制到堆栈中返回它。完全抛弃本地:
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt,mi);
gen.Emit(OpCodes.Ret);