Reputation: 7567
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?
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());
}
}
Upvotes: 4
Views: 1987
Reputation: 66573
Here is what happens in the ldftn
case. Your method creates a delegate that has:
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.
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
.
Upvotes: 8