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

发布于 2024-10-08 00:44:26 字数 2106 浏览 2 评论 0原文

下面的示例程序是我试图掌握 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());
    }
}

The following example program is my trying to grasp the usage of the ldvirtftn opcode

You see the name suggests this is the opcode to use when loading a virtual function pointer on to the stack. In the example code, I'm creating a type with 2 static methods Ldftn and Ldvirtftn, both of these methods return an open delegate of Base.Method() the first function Ldftn uses the ldftn opcode, and works unexpectedly, as Base.Method is virtual. The second method uses Ldvirtftn and apparently created an invalid program.

What am I doing wrong? What is the purpose of this opcode?

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());
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北风几吹夏 2024-10-15 00:44:26
  1. 以下是 ldftn 案例中发生的情况。您的方法创建一个委托,该委托具有:

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

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

    func()(new Child());
    

    CLR 使用新的 Child 实例作为“第一个参数”。由于您调用的方法不是静态的,因此第一个参数将成为 this 指针。因此,这个调用就相当于

    new Child().Method();
    

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

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

您可以尝试进行以下更改来了解发生的情况:

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

  • 通过将 Action 更改为简单的 Action 或通过使 Method() 接受范围。在这两种情况下,您可能很快就会发现它符合您的预期。特别是,您会发现使用 ldftn 创建的委托将始终调用 Base.Method(),即使您使用 Child 实例创建它也是如此>.

  1. Here is what happens in the ldftn case. Your method creates a delegate that has:

    • no first argument (usually used only for static methods);
    • Base.Method() as the method (which is not static).

    You create this delegate as Action<Base>, which happens to have one parameter. When you call this delegate in this line:

    func()(new Child());
    

    the CLR uses the new Child instance as the “first argument”. Since the method you are calling is not static, the first argument becomes the this pointer. Thus, this call becomes equivalent to

    new Child().Method();
    

    and this causes a separate virtual-method dispatch at invoke time (not at ldftn time), so Child.Method() gets called. This is why it prints “Child” instead of the “Base” that you probably expected.

  2. In the ldvirtftn case, you are getting an invalid program because you forgot that ldvirtftn requires an object reference on the stack while ldftn doesn’t.

You could try making the following changes to understand what’s going on:

  • Instead of null, pass an actual instance of Base or Child to the delegate constructor, as is customary for non-static methods. You will find that it will then refuse to create the delegate because the number of parameters no longer match (Action<Base> requires one parameter, but Method() has none).

  • Make the number of parameters match, either by changing Action<Base> to simply Action, or by making Method() accept a parameter. In both cases, you will probably quickly find that it does what you expect. In particular, you will find that the delegate created with ldftn will always call Base.Method() even if you created it with an instance of Child.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文