Invoking a virtual method non-virtually
Method calls using the C# ‘base’ keyword get compiled to an IL ‘call’ instruction, rather than the ‘callvirt’ that 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=1067Anonymous
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