.net - 鉴于我手头有所需的 MethodInfo 和实例类型,如何使用 OpCodes.Callvirt 发出 OpCodes.Constrained

标签 .net f# reflection.emit il

我有一个递归函数 emit : Map<string,LocalBuilder> -> exp -> unit哪里il : ILGenerator对函数来说是全局的,并且 exp是一个判别联合,表示带有大小写 InstanceCall of exp * MethodInfo * exp list * Type 的类型检查的解析语言和 Typeexp 上的属性(property)表示表达式的类型。

在下面的片段中,我试图为一个实例调用发出 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)
        ...

最佳答案

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

但文档也说:

The constrained opcode allows IL compilers to make a call to a virtual function in a uniform way independent of whether ptr is a value type or a reference type. Although it is intended for the case where thisType is a generic type variable, the constrained prefix also works for nongeneric types and can reduce the complexity of generating virtual calls in languages that hide the distinction between value types and reference types. ...

Using the constrained prefix also avoids potential versioning problems with value types. If the constrained prefix is not used, different IL must be emitted depending on whether or not a value type overrides a method of System.Object. For example, if a value type V overrides the Object.ToString() method, a call V.ToString() instruction is emitted; if it does not, a box instruction and a callvirt Object.ToString() instruction are emitted. A versioning problem can arise in the former case if the override is later removed, and in the latter case if an override is later added.



小示范(我很惭愧,我的上网本上没有 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

关于.net - 鉴于我手头有所需的 MethodInfo 和实例类型,如何使用 OpCodes.Callvirt 发出 OpCodes.Constrained,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6884149/

相关文章:

.net - 如何在 dotnet 核心中运行 F# interactive (fsi.exe)?现在还支持吗?

c# - 是否可以防止 Visual Studio 在 BackgroundWorker.DoWork 内出现异常时中断

.net - 在 .net 中绘制科学数据

charts - 对于此 Fsharp Charting Livechart 示例,为什么未设置引用?

f# - 将联合类型区分为单一类型的过滤列表

.net - 将 nNHibernate 与发出的代码一起使用

c# - IProgress<T> 未触发

.net - 使用 F# 控制台 onExit 事件

c# - 反编译动态创建的类/类型

c# - 使用 Reflection.Emit 以编程方式为现有类属性添加新属性