C# 何时以及如何使用LDVIRTTN操作码?

C# 何时以及如何使用LDVIRTTN操作码?,c#,.net,reflection.emit,il,C#,.net,Reflection.emit,Il,下面的示例程序是我试图掌握ldvirtftnopcode的用法的程序。您可以看到,名称表明这是在堆栈上加载虚拟函数指针时要使用的操作码。在示例代码中,我使用两个静态方法创建了一个类型Ldftn和Ldvirtftn,这两个方法都返回一个打开的委托Base.Method()第一个函数Ldftn使用Ldftn操作码,并意外工作,因为Base.Method是虚拟的。第二种方法使用Ldvirtftn,显然创建了一个无效的程序。我做错了什么?除了混淆之外,此操作码的目的是什么 public class Ba

下面的示例程序是我试图掌握
ldvirtftn
opcode的用法的程序。您可以看到,名称表明这是在堆栈上加载虚拟函数指针时要使用的操作码。在示例代码中,我使用两个静态方法创建了一个类型
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());
    }
}
公共类基
{
公共虚空方法()
{
控制台。写入线(“基”);
}
}
公共类子类:基
{
公共重写无效方法()
{
控制台。书写线(“儿童”);
}
}
班级计划
{
静态void Main(字符串[]参数)
{
AssemblyBuilder ab=AppDomain.CurrentDomain.DefinedDynamicAssembly(新的AssemblyName(“测试”),AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb=ab.DefinedDynamicModule(“测试模块”);
TypeBuilder tb=mb.DefineType(“TestType”);
MethodBuilder method=tb.DefineMethod(“Ldftn”,MethodAttributes.Public | MethodAttributes.Static,typeof(Action),Type.EmptyTypes);
var ilgen=method.GetILGenerator();
ilgen.Emit(操作码.Ldnull);
Emit(opcode.Ldftn,typeof(Base.GetMethod)(“Method”);
Emit(opcode.Newobj,typeof(Action.GetConstructors()[0]);
ilgen.Emit(操作码Ret);
method=tb.DefineMethod(“Ldvirtftn”,MethodAttributes.Public | MethodAttributes.Static,typeof(Action),Type.EmptyTypes);
ilgen=method.GetILGenerator();
ilgen.Emit(操作码.Ldnull);
Emit(opcode.Ldvirtftn,typeof(Base.GetMethod)(“方法”);
Emit(opcode.Newobj,typeof(Action.GetConstructors()[0]);
ilgen.Emit(操作码Ret);
var type=tb.CreateType();
var func=Delegate.CreateDelegate(typeof(func),tb.GetMethod(“Ldftn”))作为func;
var func2=Delegate.CreateDelegate(typeof(Func),tb.GetMethod(“Ldvirtftn”))作为Func;
func()(新子项());
func2()(新子项());
}
}
  • 下面是在
    ldftn
    案例中发生的情况。您的方法创建的委托具有:

    • 没有第一个参数(通常只用于静态方法)
    • Base.Method()
      作为方法(不是静态的)
    您将此委托创建为
    操作
    ,它恰好有一个参数。在此行中调用此代理时:

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

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

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

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

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

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


    实际上,虚拟调度正是我想要的。我不知道virtftn只在传递一个实例时工作,所以我应该发送到go:ld instance、dup、ldvirtftn Method、newobj delegate ctor?奇怪的是,一个静态
    string->int
    函数可以用来创建“static”
    Func
    委托和一个“instance”
    Func
    委托。只需
    ldnull
    (或
    ldstr
    )+
    ldftn
    +
    newobj
    ,使用任一委托类型即可。