何时以及如何使用Ldvirtftn操作码?

时间:2010-12-16 01:06:28

标签: c# .net reflection.emit il

以下示例程序是我试图掌握ldvirtftn操作码的用法。您会看到名称表明这是在将虚函数指针加载到堆栈时使用的操作码。在示例代码中,我创建了一个包含2个静态方法LdftnLdvirtftn的类型,这两个方法都返回Base.Method()的开放委托,第一个函数Ldftn使用ldftn操作码,并且意外工作,因为Base.Method是虚拟的。第二种方法使用Ldvirtftn,显然创建了一个无效的程序。我究竟做错了什么?除了困惑之外,这个操作码的目的是什么?

public class Base
{
    public virtual void Method()
    {
        Console.WriteLine("Base");
    }
}

public class Child : Base
{
    public override void Method()
    {
        Console.WriteLine("Child");
    }
}
class Program
{
    static void Main(string[] args)
    {
        AssemblyBuilder ab =AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"),AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder mb = ab.DefineDynamicModule("TestModule");
        TypeBuilder tb = mb.DefineType("TestType");
        MethodBuilder method = tb.DefineMethod("Ldftn",MethodAttributes.Public | MethodAttributes.Static, typeof(Action<Base>), Type.EmptyTypes);
        var ilgen = method.GetILGenerator();
        ilgen.Emit(OpCodes.Ldnull);
        ilgen.Emit(OpCodes.Ldftn, typeof(Base).GetMethod("Method"));
        ilgen.Emit(OpCodes.Newobj, typeof(Action<Base>).GetConstructors()[0]);
        ilgen.Emit(OpCodes.Ret);
        method = tb.DefineMethod("Ldvirtftn", MethodAttributes.Public | MethodAttributes.Static, typeof(Action<Base>), Type.EmptyTypes);
        ilgen = method.GetILGenerator();
        ilgen.Emit(OpCodes.Ldnull);
        ilgen.Emit(OpCodes.Ldvirtftn, typeof(Base).GetMethod("Method"));
        ilgen.Emit(OpCodes.Newobj, typeof(Action<Base>).GetConstructors()[0]);
        ilgen.Emit(OpCodes.Ret);
        var type = tb.CreateType();
        var func = Delegate.CreateDelegate(typeof(Func<Action<Base>>),tb.GetMethod("Ldftn")) as Func<Action<Base>>;
        var func2 = Delegate.CreateDelegate(typeof(Func<Action<Base>>), tb.GetMethod("Ldvirtftn")) as Func<Action<Base>>;
        func()(new Child());
        func2()(new Child());
    }
}

1 个答案:

答案 0 :(得分:8)

  1. 以下是ldftn案例中发生的情况。您的方法创建一个具有以下内容的委托:

    • 没有第一个参数(通常仅用于静态方法);
    • Base.Method()作为方法(静态)。

    您将此委托创建为Action<Base>,恰好有一个参数。当您在此行中调用此委托时:

    func()(new Child());
    

    CLR使用新的Child实例作为“第一个参数”。由于您调用的方法是非静态,因此第一个参数变为this指针。因此,此调用等同于

    new Child().Method();
    

    这会导致在调用时单独的虚拟方法调度 (而不是在ldftn时间),因此调用Child.Method()。这就是为什么它打印“Child”而不是你可能期望的“Base”。

  2. ldvirtftn的情况下,您收到的程序无效,因为您忘记ldvirtftn需要堆栈上的对象引用而ldftn没有。

  3. 您可以尝试进行以下更改以了解正在发生的事情:

    • 而不是null,将BaseChild的实际实例传递给委托构造函数,这是非静态方法的惯例。您会发现它将拒绝创建委托,因为参数的数量不再匹配(Action<Base>需要一个参数,但Method()没有)。

    • 通过将Action<Base>更改为Action或使Method()接受参数,使参数数量匹配。在这两种情况下,您可能会很快发现它符合您的期望。特别是,您会发现使用ldftn创建的委托将始终致电Base.Method(),即使您使用Child的实例创建了该委托。