我有一个递归函数emit : Map<string,LocalBuilder> -> exp -> unit
,il : ILGenerator
它是函数的全局函数,exp
是一个判别联合,表示带大小写的类型检查解析语言,InstanceCall of exp * MethodInfo * exp list * Type
并且Type
是exp
表示表达式类型的属性。
在下面的片段中,我试图为实例调用发出 IL 操作码,其中instance.Type
可能是也可能不是ValueType
. 所以我知道我可以OpCodes.Constrained
用来灵活有效地对引用、值和枚举类型进行虚拟调用。一般来说,我是 Reflection.Emit 和机器语言的新手,因此理解链接文档对OpCodes.Constrained
我来说并不强大。
这是我的尝试,但它会导致VerificationException
“操作可能会破坏运行时的稳定性。”:
let rec emit lenv ast =
match ast with
...
| InstanceCall(instance,methodInfo,args,_) ->
instance::args |> List.iter (emit lenv)
il.Emit(OpCodes.Constrained, instance.Type)
il.Emit(OpCodes.Callvirt, methodInfo)
...
查看文档,我认为关键可能是“托管指针 ptr 被压入堆栈。ptr 的类型必须是 thisType 的托管指针 (&)。请注意,这与无前缀的情况不同callvirt 指令,它需要 thisType 的引用。”
更新
谢谢@Tomas 和@desco,我现在明白什么时候使用OpCodes.Constrained
(instance.Type
是ValueType,但是methodInfo.DeclaringType
是引用类型)。
但事实证明我还不需要考虑这种情况,我真正的问题是堆栈上的实例参数:我只花了 6 个小时就知道它需要一个地址而不是值(查看 DLR 源代码给了我线索,然后在一个简单的 C# 程序上使用 ilasm.exe 就清楚了)。
这是我的最终工作版本:
let rec emit lenv ast =
match ast with
| Int32(x,_) ->
il.Emit(OpCodes.Ldc_I4, x)
...
| InstanceCall(instance,methodInfo,args,_) ->
emit lenv instance
//if value type, pop, put in field, then load the field address
if instance.Type.IsValueType then
let loc = il.DeclareLocal(instance.Type)
il.Emit(OpCodes.Stloc, loc)
il.Emit(OpCodes.Ldloca, loc)
for arg in args do emit lenv arg
if instance.Type.IsValueType then
il.Emit(OpCodes.Call, methodInfo)
else
il.Emit(OpCodes.Callvirt, methodInfo)
...