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());
}
}
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 thethis
pointer. Thus, this call becomes equivalent tonew 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.In the
ldvirtftn
case, you are getting an invalid program because you forgot thatldvirtftn
requires an object reference on the stack whileldftn
doesn’t.
You could try making the following changes to understand what’s going on:
Instead of
null
, pass an actual instance ofBase
orChild
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, butMethod()
has none).Make the number of parameters match, either by changing
Action<Base>
to simplyAction
, or by makingMethod()
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 withldftn
will always callBase.Method()
even if you created it with an instance ofChild
.
精彩评论