.net – 如何发出OpCodes.Constrained与OpCodes.Callvirt给定我有所需的MethodInfo和实例类型在手

我有一个递归函数emit:Map< string,LocalBuilder> – > exp – >其中il:ILGenerator对函数是全局的单位,exp是表示经过类型检查的解析语言的判别式联合,例如InstanceCall为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)
        ...
最佳答案
我认为你在问题的最后引用的一些文档是问题的根源.我不太确定OpCodes.Constrained的前缀是什么(我不比你更了解文档),但我试着看看它是如何被微软使用的:-).

以下是source code of Dynamic Language Runtime的一个片段,它发出了一个方法调用:

// Emit arguments
List<WriteBack> wb = EmitArguments(mi, args);

// Emit the actual call
OpCode callOp = UseVirtual(mi) ? OpCodes.Callvirt : OpCodes.Call;
if (callOp == OpCodes.Callvirt && objectType.IsValueType) {
    // This automatically boxes value types if necessary.
    _ilg.Emit(OpCodes.Constrained, objectType);
}
// The method call can be a tail call if [...]
if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail && 
    !MethodHasByRefParameter(mi)) {
    _ilg.Emit(OpCodes.Tailcall);
}
if (mi.CallingConvention == CallingConventions.VarArgs) {
    _ilg.EmitCall(callOp, mi, args.Map(a => a.Type));
} else {
    _ilg.Emit(callOp, mi);
}

// Emit writebacks for properties passed as "ref" arguments
EmitWriteBack(wb);

我想你可能想要遵循他们的行为 – 似乎约束前缀仅用于值类型的虚拟调用.我的解释是,对于值类型,您知道什么是实际类型,因此您不需要实际(不受约束的)虚拟调用.

转载注明原文:.net – 如何发出OpCodes.Constrained与OpCodes.Callvirt给定我有所需的MethodInfo和实例类型在手 - 代码日志