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

时间:2011-07-30 15:30:47

标签: .net f# reflection.emit il

我有一个递归函数emit : Map<string,LocalBuilder> -> exp -> unit,其中il : ILGenerator是函数的全局函数,而exp是一个判别式联合,表示一个带有案例InstanceCall of exp * MethodInfo * exp list * Type和{的类型检查解析语言{1}}是Type上的一个属性,表示表达式的类型。

在下面的片段中,我试图为实例调用发出IL操作码,其中exp可能是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的托管指针(&amp;)。请注意,这与一个没有前缀的callvirt指令的情况,它需要thisType的引用。“

更新

感谢@Tomas和@desco,我现在知道何时使用OpCodes.Constrainedinstance.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)
        ...

2 个答案:

答案 0 :(得分:3)

基本上我同意Tomas:如果你在编译时知道确切的类型,那么你可以自己发出正确的调用指令。约束前缀通常用于通用代码

但文件也说:

  

受约束的操作码允许IL编译器以统一的方式调用虚函数,而与ptr是值类型还是引用类型无关。虽然它适用于thisType是泛型类型变量的情况,但约束前缀也适用于非泛型类型,并且可以降低在隐藏值类型和引用类型之间的区别的语言中生成虚拟调用的复杂性。   ...

     

使用约束前缀还可以避免值类型的潜在版本控制问题。如果未使用约束前缀,则必须发出不同的IL,具体取决于值类型是否覆盖System.Object的方法。例如,如果值类型V覆盖Object.ToString()方法,则会发出调用V.ToString()指令;如果没有,则发出一个box指令和一个callvirt Object.ToString()指令。如果稍后删除覆盖,则在前一种情况下可能出现版本控制问题,而在后一种情况下,如果稍​​后添加覆盖则会出现版本控制问题。

小型演示(对我来说很遗憾,我的上网本上没有F#):

using System;
using System.Reflection;
using System.Reflection.Emit;

public struct EvilMutableStruct
{
    int i;
    public override string ToString()
    {
            i++;
            return i.ToString();
    }
}

class Program
{
    public static void Main()
    {
            var intToString = Make<int>();
            var stringToString = Make<string>();
            var structToString = Make<EvilMutableStruct>();
            Console.WriteLine(intToString(5));
            Console.WriteLine(stringToString("!!!"));   
            Console.WriteLine(structToString (new EvilMutableStruct())); 
    }

    static MethodInfo ToStringMethod = new Func<string>(new object().ToString).Method;
    static MethodInfo ConcatMethod = new Func<string, string, string>(String.Concat).Method;

    // x => x.ToString() + x.ToString()
    private static Func<T, string> Make<T>()
    {
            var dynamicMethod = new DynamicMethod("ToString", typeof(string), new[] {typeof(T)});
            var il = dynamicMethod.GetILGenerator();

            il.Emit(OpCodes.Ldarga_S, 0);
            il.Emit(OpCodes.Constrained, typeof(T));
            il.Emit(OpCodes.Callvirt, ToStringMethod);

            il.Emit(OpCodes.Ldarga_S, 0);
            il.Emit(OpCodes.Constrained, typeof(T));
            il.Emit(OpCodes.Callvirt, ToStringMethod);

            il.Emit(OpCodes.Call, ConcatMethod);

            il.Emit(OpCodes.Ret);
            return (Func<T, string>)dynamicMethod.CreateDelegate(typeof(Func<T, string>));
     }
}

输出:

55
!!!!!!
12

答案 1 :(得分:1)

我认为您在问题末尾引用的文档是问题的根源。我不太确定OpCodes.Constrained前缀是什么(我不比你更了解文档),但我试着看看它是如何被Microsoft使用的: - )。

以下是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);

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