下面的示例程序是我试图掌握ldvirtftn
操作码的用法
你可以看到这个名字暗示了这是在将虚函数指针加载到堆栈上时要使用的操作码。在示例代码中,我使用两个静态方法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());
}
}
1条答案
按热度按时间wlzqhblo1#
ldftn
的例子。您的方法创建一个委托,该委托具有:Base.Method()
作为方法(它不是静态的)。您将此委托创建为
Action<Base>
,它恰好有一个参数。当您在此行中调用此委托时:CLR使用新的
Child
示例作为“第一个参数”。因为你调用的方法不是静态的,所以第一个参数变成了this
指针。因此,此调用将等效于并且这导致单独的虚拟方法分派 * 在调用时间 *(而不是在ldftn时间),因此
Child.Method()
被调用。这就是为什么它打印“Child”而不是您可能期望的“Base”。ldvirtftn
的例子中,你得到了一个无效的程序,因为你忘记了ldvirtftn
需要堆栈上的对象引用,而ldftn
不需要。您可以尝试进行以下更改以了解发生了什么:
Base
或Child
的实际示例传递给委托构造函数,而不是null
。您会发现它会拒绝创建委托,因为参数的数量不再匹配(Action<Base>
需要一个参数,但Method()
没有)。Action<Base>
简单地更改为Action
或使Method()
接受一个参数,使参数的数量匹配。在这两种情况下,您可能很快就会发现它确实符合您的预期。特别是,您会发现使用ldftn
创建的委托将始终调用Base.Method()
,即使您使用Child
的示例创建它。