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

7gcisfzg  于 2023-05-08  发布在  .NET
关注(0)|答案(1)|浏览(158)

下面的示例程序是我试图掌握ldvirtftn操作码的用法
你可以看到这个名字暗示了这是在将虚函数指针加载到堆栈上时要使用的操作码。在示例代码中,我使用两个静态方法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());
    }
}
wlzqhblo

wlzqhblo1#

  • 下面是ldftn的例子。您的方法创建一个委托,该委托具有:
  • 没有第一个参数(通常只用于静态方法);
  • Base.Method()作为方法(它不是静态的)。

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

func()(new Child());

CLR使用新的Child示例作为“第一个参数”。因为你调用的方法不是静态的,所以第一个参数变成了this指针。因此,此调用将等效于

new Child().Method();

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

  • ldvirtftn的例子中,你得到了一个无效的程序,因为你忘记了ldvirtftn需要堆栈上的对象引用,而ldftn不需要。

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

  • 按照非静态方法的惯例,将BaseChild的实际示例传递给委托构造函数,而不是null。您会发现它会拒绝创建委托,因为参数的数量不再匹配(Action<Base>需要一个参数,但Method()没有)。
  • 通过将Action<Base>简单地更改为Action或使Method()接受一个参数,使参数的数量匹配。在这两种情况下,您可能很快就会发现它确实符合您的预期。特别是,您会发现使用ldftn创建的委托将始终调用Base.Method(),即使您使用Child的示例创建它。

相关问题