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

Mic*_*l B 3 .net c# il reflection.emit

以下示例程序是我试图掌握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());
    }
}
Run Code Online (Sandbox Code Playgroud)

Tim*_*mwi 8

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

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

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

    func()(new Child());
    
    Run Code Online (Sandbox Code Playgroud)

    CLR使用新Child实例作为"第一个参数".由于您调用的方法不是静态的,因此第一个参数成为this指针.因此,这个呼叫变得等同于

    new Child().Method();
    
    Run Code Online (Sandbox Code Playgroud)

    这会在调用时(不是在ldftn时间)导致单独的虚方法调度,因此Child.Method()被调用.这就是它打印"Child"而不是你可能期望的"Base"的原因.

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

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

  • 相反的null,通过一个实际的实例BaseChild到委托的构造函数,如通常用于非静态方法.您会发现它将拒绝创建委托,因为参数的数量不再匹配(Action<Base>需要一个参数,但Method()没有参数).

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