次の方法で共有


Invoking a virtual method non-virtually

Method calls using the C# ‘base’ keyword get compiled to an IL ‘call’ instruction, rather than the ‘callvirtthat is normally used. This is the one case in C# where a virtual method can be invoked without virtual dispatch. The CLR allows it to be used generally for non-virtual calls, but it’s unverifiable in other cases (see section 3.19 of the CLI specification partition III for the full definition).

One place this causes some pain is with func-eval. Func-Eval always does a virtual dispatch on virtual methods, which is why the ‘base’ keyword doesn’t work properly in the watch/immediate window in VS 2008. Mike Stall has a blog entry on this topic here: https://blogs.msdn.com/jmstall/archive/2006/06/29/funceval-does-virtual-dispatch.aspx

What Mike doesn’t mention is there is actually a work-around for this. First we need some mechanism to invoke a virtual method non-virtually, and the only one I could find was using Reflection.Emit / lightweight-codegen to emit a ‘call’ instruction. For example, the following method can be used to invoke any virtual method non-virtually:

 /// <summary>
/// Call a virtual method non-virtually - like Reflection's MethodInfo.Invoke, 
/// but doesn't do virtual dispatch.
/// </summary>
/// <param name="method">The method to invoke</param>
/// <param name="args">The arguments to pass (including 'this')</param>
/// <returns>The return value from the call</returns>
static object InvokeNonVirtual(MethodInfo method, object[] args)
{
    // Reflection doesn't seem to have a way directly (eg. custom binders are 
    // only used for ambiguities).  Using a delegate also always seems to do 
    // virtual dispatch.

    // Use LCG to generate a temporary method that uses a 'call' instruction to
    // invoke the supplied method non-virtually.
    // Doing a non-virtual call on a virtual method outside the class that 
    // defines it will normally generate a VerificationException (PEVerify 
    // says "The 'this' parameter to the call must be the callng method's 
    // 'this' parameter.").  By associating the method with a type ("Program") 
    // in a full-trust assembly, we tell the JIT to skip this verification step.
    // Alternately we might want to associate it with method.DeclaringType - the
    // verification might then pass even if it's not skipped (eg. partial trust).
    var paramTypes = new List<Type>();
    if (!method.IsStatic)
        paramTypes.Add(method.DeclaringType);
    paramTypes.AddRange(method.GetParameters().Select(p => p.ParameterType));
    DynamicMethod dm = new DynamicMethod(
        "NonVirtualInvoker",    // name
        method.ReturnType,      // same return type as method we're calling 
        paramTypes.ToArray(),   // same parameter types as method we're calling
        typeof(Program));       // associates with this full-trust code
    ILGenerator il = dm.GetILGenerator();
    for (int i = 0; i < paramTypes.Count; i++)
        il.Emit(OpCodes.Ldarg, i);             // load all args
    il.EmitCall(OpCodes.Call, method, null);   // call the method non-virtually
    il.Emit(OpCodes.Ret);                      // return what the call returned

    // Call the emitted method, which in turn will call the method requested
    return dm.Invoke(null, args);
}

With a method like the above available, we can use statements like the following (in the program or in the immediate window) to invoke a method non-virtually (i.e. to simulate the ‘base’ keyword):

 // Foo is some class that overrides ToString
Foo f = new Foo();
MethodInfo m = typeof(object).GetMethod("ToString", 
    BindingFlags.Instance | BindingFlags.Public);

// s1 will be the same as f.ToString - whatever Foo.ToString returns
string s1 = (string)m.Invoke(f, 
    BindingFlags.Instance | BindingFlags.InvokeMethod, null, null, null);

// s2 will be the return value from Object.ToString
string s2 = (string)InvokeNonVirtual(m, new object[] {f});

We can also avoid having to get the ‘InvokeNonVirtual’ method into the target program at all by entering code like the following into the immediate window (depending on what method you want to invoke, eg. this invokes 'ToString' non-virtually on object 'o').:

 MethodInfo m1 = typeof(object).GetMethod("ToString", 
    BindingFlags.Instance | BindingFlags.Public);
DynamicMethod dm = new DynamicMethod("ToStringInvoker", 
    typeof(string), new Type[] { typeof(object) }, typeof(Program));
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, m1, null);
il.Emit(OpCodes.Ret);
string s3 = (string)dm.Invoke(null, new object[] { o });

Yes, I realize typing this into the immediate window whenever you want to say ‘base’ is kind of a pain (perhaps completely impractical). But the interesting thing here is that it means a debugger could build support for the ‘base’ keyword without any additional work from ICorDebug or the CLR! Of course, I still agree with Mike that it’s a reasonable feature-request to have func-eval support an option for non-virtual dispatch. I just don’t see it making its way to the top of our wish-list anytime soon (unless we get strong customer feedback of course).

This also raises an interesting question about the design of func-eval. Should we have just supported calling a few key reflection APIs and made all evals go through reflection? This certainly would have been simpler and more reliable for the CLR – otherwise we have to duplicate a lot of the logic about how to marshal values and call methods that Reflection already needs to have. Then again, there’s always going to be the challenge of marshalling between ICorDebugValue instances and objects in the target, and that’s a pretty big chunk of what’s required to make func-eval work.

Comments

  • Anonymous
    August 16, 2008
    PingBack from http://housesfunnywallpaper.cn/?p=1067

  • Anonymous
    August 22, 2008
    I was investigating non-virtual calls the other day, because DynamicILGenerator overrides some ILGenerator's methods with functions throwing NotSupportedExceptions for no reason I could discover and I had to call them. At first I developed a solution with dynamic methods, very similar to what you propose here, but actually there is a way do do it with delegates. The relevant code looks like this:    private delegate void BeginFaultBlock  () ;    private readonly static ConstructorInfo dBeginFaultBlock  = typeof (BeginFilterBlock).GetConstructors () [0] ;    private readonly static IntPtr          iBeginFaultBlock  = typeof (ILGenerator).GetMethod ("BeginFaultBlock")       .MethodHandle.GetFunctionPointer () ;    public static BeginFaultBlock MakeBeginFaultBlockDelegate (ILGenerator generator)    {        return (BeginFaultBlock) dBeginFaultBlock.Invoke (new object[] { generator, iBeginFaultBlock  })) () ;    } I couldn't get invocation through delegates to work reliably when the delegate receives the generator as an argument, i.e.    private delegate void BeginFaultBlock (ILGenerator generator) ; // no good but maybe UnboundDelegate can handle this.

  • Anonymous
    August 25, 2008
    The comment has been removed