Want to show your appreciation? Please to my charity.

Friday, July 17, 2009

Intercept Explicit Interface Implementation with Inheritance Based Proxy

This is continue from my last post, Intercept Non-Virtual Interface Implementation, Implicit or Explicit.

Krzysztof was absolutely right that it throws MethodAccessException if you just call it. It only works with delegate. But how difficult and/or expensive it is to use delegate?

  1. For each explicit method that we want to intercept, we need to define a new delegate type that matches the method signature
  2. We need to defined a static field of above generated delegate type for each method
  3. Then we use the static delegate instance to make the call to the explicit implementation method in the parent class

This doesn't sound bad to me. There is nearly no runtime CPU overhead, but to hold the generated delegate types, we need some memory. I believe this is well acceptable. After all, we can further optimized it by caching the generated delegate to be reused by the methods with same signature.

I took this as a good opportunity for me to learn the Reflection.Emit API. Thanks to Krzysztof's for the article Working effectively with Reflection.Emit, which led me to the proper tools and approach to start with. Here is what I have achieved.

        public interface IFoo
        {
            void FooMethod(out int i);
            int BarMethod(int i);
        }

        public class Foo : IFoo
        {
            void IFoo.FooMethod(out int i)
            {
                Console.WriteLine("From Foo.FooMethod!");
                i = 100;
            }

            int IFoo.BarMethod(int i)
            {
                return i*i;
            }
        }

        public static void Main(string[] args)
        {
            const string moduleName = "DynamicModule";
            ModuleBuilder mb = EmitUtils.CreateDynamicModule(moduleName);

            Type t = OverrideFoo(mb);

            var proxy = (IFoo) t.GetConstructor(Type.EmptyTypes).Invoke(null);

            int result;
            proxy.FooMethod(out result);
            Console.WriteLine(result);
            Console.WriteLine(proxy.BarMethod(5));
        }

        private static Type OverrideFoo(ModuleBuilder mb)
        {
            TypeBuilder tb = mb.DefineType(
                mb.Assembly.GetName().Name + ".Bar", 
TypeAttributes.Public | TypeAttributes.Class, typeof(Foo), new Type[]{typeof(IFoo)}); var iFooMethods = typeof (IFoo).GetMethods(); var overriders = new List<ExplicitMethodOverrider>(iFooMethods.Length); foreach (MethodInfo iFooMethod in iFooMethods) { overriders.Add(new ExplicitMethodOverrider(mb, tb, iFooMethod)); } Type t = tb.CreateType(); // Initialize static fields for delegates foreach (ExplicitMethodOverrider overrider in overriders) { overrider.InitializeDelegate(t); } return t; }

The output of above program is:

Proxy before call Void DynamicProxy.Program.IFoo.FooMethod(Int32 ByRef)
From Foo.FooMethod!
Proxy after call Void DynamicProxy.Program.IFoo.FooMethod(Int32 ByRef)
100
Proxy before call Int32 DynamicProxy.Program.IFoo.BarMethod(Int32)
Proxy after call Int32 DynamicProxy.Program.IFoo.BarMethod(Int32)
25

The core of this is consisted of two piece of code. One emits the delegate type based on a the MethodInfo of the explicitly implemented base class method. This is done by a static method EmitUtils.GenerateDelegateType, mostly copy from Joel Pobar. Another one emits a static field of delegate type and implement the interface method to call the delegate, which in turn calls the explicit implementation in the base class. This logic is in the the ExplicitMethodOverrider class on which I spent most of my time. The ExplicitMethodOverrider class also provide an initialization method to populate the static field after the type is created.

All the source code can be found here.

2 comments:

Anonymous said...

Hi Kenneth, can you suggest a way to intercept non-virtual members? Thanks.

Kenneth Xu said...

Well, in this and previous blogs, we were talking about intercepting non-virtual members, but they has to be the implementation of an interface and you have to access through the interface. Otherwise, you'll have to use Profiler API to intercept and redirect the call. That is what Typemock does. I'm not familiar what that approach.

Post a Comment