3

以下示例程序是我试图掌握ldvirtftn操作码的用法。您会看到名称表明这是在将虚拟函数指针加载到堆栈时使用的操作码。在示例代码中,我创建了一个具有 2 个静态方法的类型,Ldftn并且Ldvirtftn这两个方法都返回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());
    }
}
4

1 回答 1

8
  1. 以下是本案发生的ldftn情况。您的方法创建了一个委托,该委托具有:

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

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

    func()(new Child());
    

    CLR 使用新Child实例作为“第一个参数”。由于您调用的方法不是 static,因此第一个参数成为this指针。因此,此调用等效于

    new Child().Method();
    

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

  2. 在这种ldvirtftn情况下,您会得到一个无效的程序,因为您忘记了ldvirtftn需要堆栈上的对象引用ldftn而不需要。

您可以尝试进行以下更改以了解发生了什么:

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

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

于 2010-12-16T01:47:01.253 回答