开发者

When and how do I use the Ldvirtftn opcode?

开发者 https://www.devze.com 2023-01-30 15:32 出处:网络
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 t

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, other than confusion?

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.DefineMeth开发者_运维问答od("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. 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消