Saturday, June 21, 2014

Interface Proxy with Emit

You can find this project here: https://github.com/DataDink/Proxy

Before you read any further please see these much more tested and performant proxy solutions:
Be warned: What you are about to see is untested and most likely unperformant, unstable, unfriendly, and unsupported. Don't say I didn't warn you...

However, if you are like me and you just want a tiny solution to call your own, or even just wanting to learn something about dynamic proxy generation then please proceed!

Today I decided to try my hand at making a very simple, single-class proxy. The purpose for something like this would be to be able to implement an interface at run time to either wrap some kind of communication / IO layer or proxy an existing instance of that interface allowing me to intercept method and property interactions.

For example: Let's say I have an application that has a bunch of services that it interacts with. After the application is already written I decide I need to start logging information about each call to my services. I really don't want to go into every method on every service and add calls to a logger. Nor do I want to change the way my application interacts with my services. I'm way too lazy for that!

One annoying way to solve this problem would be to write a whole new class for each service interface that keeps an instance of the actual service and forwards calls to it after logging. Not the most desirable solution since I now have to write even more code than I was when I was just adding logging to the actual service methods. On top of this I would now have 2 classes to maintain if the service's interface is ever added to or changed.

What I really want to do is automate or generate this wrapper dynamically, yet still be able to function in a strongly typed environment. As I've stated before there are a number of existing solutions that will allow me to do this, but for this post I wanted to dive a little deeper and see how this stuff works for myself.

What it does:

Below I have an abstract Proxy<T> base class. This doesn't need to be abstract, but isn't terribly useful by itself as I have written it. The point of this implementation would be to extend this to a concrete class that is more useful for a specific use-case (of which I have several).

  1.     public abstract class Proxy<T> where T : class
  2.     {
  3.         #region Static Cache (NOTE: This is per T)
  4.         /// <summary>
  5.         /// The dynamic proxy type 
  6.         /// </summary>
  7.         protected static readonly Type InstanceType;
  8.         /// <summary>
  9.         /// The type of T
  10.         /// </summary>
  11.         protected static readonly Type Interface;
  12.         private static readonly Dictionary<intMethodInfo> MethodIndex;
  13.         /// <summary>
  14.         /// Looks up the MethodInfo for type T by index
  15.         /// </summary>
  16.         public static MethodInfo Lookup(int index) { return MethodIndex[index]; }
  17.         private static AssemblyBuilder Assembly;
  18.         /// <summary>
  19.         /// Saves the underlying generated assembly to disk
  20.         /// </summary>
  21.         public static void Save() { Assembly.Save(Assembly.GetName().Name + ".dll"); }
  22.         static Proxy()
  23.         {
  24.             Interface = typeof(T);
  25.             if (!Interface.IsInterface) throw new InvalidOperationException(string.Format("{0} is not an interface", Interface.Name));
  26.             MethodIndex = Interface.GetMethods().Select((m, i) => new {m, i}).ToDictionary(i => i.i, m => m.m);
  27.             InstanceType = Generate();
  28.         }
  29.         #endregion
  30.         /// <summary>
  31.         /// The underlying instance being proxied or null
  32.         /// </summary>
  33.         protected T Target { getprivate set; }
  34.         /// <summary>
  35.         /// The proxy instance
  36.         /// </summary>
  37.         protected T Instance { getprivate set; }
  38.         protected Proxy() { Instance = (T)Activator.CreateInstance(InstanceType, this); }
  39.         protected Proxy(T target) : this() { Target = target; } 
  40.         /// <summary>
  41.         /// Invokes the call handlers for this proxy
  42.         /// </summary>
  43.         public object Trigger(MethodInfo method, object[] parameters)
  44.         {
  45.             BeforeCall(method, parameters);
  46.             var result = OnCall(method, parameters);
  47.             AfterCall(method, parameters, result);
  48.             return result;
  49.         }
  50.         /// <summary>
  51.         /// Called prior to OnCall
  52.         /// </summary>
  53.         protected virtual void BeforeCall(MethodInfo method, object[] parameters) {}
  54.         /// <summary>
  55.         /// Called after OnCall
  56.         /// </summary>
  57.         protected virtual void AfterCall(MethodInfo method, object[] parameters, object result) {}
  58.         /// <summary>
  59.         /// When overridden should handle invoking the MethodInfo on Target if any
  60.         /// </summary>
  61.         protected virtual object OnCall(MethodInfo method, object[] parameters)
  62.         {
  63.             if (Target == nullreturn method.ReturnType.IsValueType ? Activator.CreateInstance(method.ReturnType) : null;
  64.             return method.Invoke(Target, parameters);
  65.         }
  66.         #region Emit Generation (
  67.         private static Type Generate()
  68.         {
  69.             var rootname = new AssemblyName(Interface.Name + "Proxy_" + Guid.NewGuid());
  70.             var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(rootname, AssemblyBuilderAccess.RunAndSave);
  71.             var module = assembly.DefineDynamicModule(rootname.Name, rootname.Name + ".dll");
  72.             var builder = module.DefineType(rootname.Name + ".Proxy"TypeAttributes.Class, typeof(object), new[] { typeof(T) });
  73.             var proxy = builder.DefineField("_proxy"typeof (Proxy<T>), FieldAttributes.Private);
  74.             
  75.             ConfigureConstructor(builder, proxy);
  76.             ConfigureInterface<T>(builder, proxy);
  77.             Assembly = assembly;
  78.             return builder.CreateType();
  79.         }
  80.         private static void ConfigureConstructor(TypeBuilder builder, FieldInfo proxy)
  81.         {
  82.             var ctor = builder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new[] { typeof(Proxy<T>) });
  83.             var ctorBase = typeof (object).GetConstructor(new Type[0]);
  84.             var encoder = ctor.GetILGenerator();
  85.             encoder.Emit(OpCodes.Ldarg_0);
  86.             encoder.Emit(OpCodes.Call, ctorBase);
  87.             encoder.Emit(OpCodes.Ldarg_0);
  88.             encoder.Emit(OpCodes.Ldarg_1);
  89.             encoder.Emit(OpCodes.Stfld, proxy);
  90.             encoder.Emit(OpCodes.Ret);
  91.         }
  92.         private static void ConfigureInterface<TIFace>(TypeBuilder builder, FieldInfo proxy = null)
  93.         {
  94.             var members = typeof (TIFace).GetMembers();
  95.             var methods = members.OfType<MethodInfo>().Where(m => !m.IsSpecialName).ToList();
  96.             var properties = members.OfType<PropertyInfo>().ToList();
  97.             methods.ForEach(m => ConfigureMethod(builder, proxy, m));
  98.             properties.ForEach(p => ConfigureProperty(builder, proxy, p));
  99.         }
  100.         private static void ConfigureProperty(TypeBuilder builder, FieldInfo proxy, PropertyInfo info)
  101.         {
  102.             var property = builder.DefineProperty(
  103.                 info.Name,
  104.                 info.Attributes,
  105.                 CallingConventions.HasThis,
  106.                 info.PropertyType,
  107.                 new Type[0]);
  108.             if (info.CanRead) property.SetGetMethod(ConfigureMethod(builder, proxy, info.GetGetMethod()));
  109.             if (info.CanWrite) property.SetSetMethod(ConfigureMethod(builder, proxy, info.GetSetMethod()));
  110.         }
  111.         private static MethodBuilder ConfigureMethod(TypeBuilder builder, FieldInfo proxy, MethodInfo info)
  112.         {
  113.             var index = MethodIndex.First(m => m.Value == info).Key;
  114.             var method = builder.DefineMethod(
  115.                 info.Name,
  116.                 (MethodAttributes.Public | MethodAttributes.Virtual | info.Attributes | MethodAttributes.Abstract) ^ MethodAttributes.Abstract,
  117.                 CallingConventions.HasThis,
  118.                 info.ReturnType,
  119.                 info.GetParameters().Select(p => p.ParameterType).ToArray());
  120.             var encoder = method.GetILGenerator();
  121.             ConfigureCall(encoder, proxy, info, index);
  122.             return method;
  123.         }
  124.         private static void ConfigureCall(ILGenerator encoder, FieldInfo proxy, MethodInfo method, int index)
  125.         {
  126.             var returnsVoid = method.ReturnType == typeof(void);
  127.             var returnsValue = !returnsVoid && method.ReturnType.IsValueType;
  128.             var lookup = typeof(Proxy<T>).GetMethod("Lookup"BindingFlags.Public | BindingFlags.Static);
  129.             var interceptor = proxy.FieldType.GetMethod("Trigger"BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  130.             var arguments = method.GetParameters().Select(p => p.ParameterType).ToArray();
  131.             encoder.DeclareLocal(returnsVoid ? typeof(object) : method.ReturnType); // return value (local 0)
  132.             encoder.DeclareLocal(typeof (object[])); // parameters (local 1)
  133.             encoder.Emit(OpCodes.Ldarg_0); // this.
  134.             encoder.Emit(OpCodes.Ldfld, proxy); // this._proxy
  135.             encoder.Emit(OpCodes.Ldc_I4_S, index); // index of method to lookup
  136.             encoder.Emit(OpCodes.Call, lookup); // lookup methodinfo by index
  137.             encoder.Emit(OpCodes.Ldc_I4_S, arguments.Length); // set size of parameter array
  138.             encoder.Emit(OpCodes.Newarr, typeof(object)); // create parameter array
  139.             encoder.Emit(OpCodes.Stloc_1); // store to local 1
  140.             for (var i = 0; i < arguments.Length; i++) { // load up parameter array values
  141.                 var argument = arguments[i];
  142.                 encoder.Emit(OpCodes.Ldloc_1); // get ready to add to array (local 1)
  143.                 ConfigureLdc(encoder, i); // index to add to
  144.                 ConfigureLdarg(encoder, i + 1); // get argument (index + 1)
  145.                 if (argument.IsValueType) encoder.Emit(OpCodes.Box, argument); // convert to object if needed
  146.                 encoder.Emit(OpCodes.Stelem_Ref); // push to array
  147.             }
  148.             encoder.Emit(OpCodes.Ldloc_1); // get array
  149.             encoder.Emit(OpCodes.Callvirt, interceptor); // pass stack to method
  150.             if (returnsValue) {
  151.                 encoder.Emit(OpCodes.Unbox_Any, method.ReturnType); // un-object a value
  152.             } else if (!returnsVoid) {
  153.                 encoder.Emit(OpCodes.Castclass, method.ReturnType); // cast to return type
  154.             } else {
  155.                 encoder.Emit(OpCodes.Pop); // discard return value
  156.             }
  157.             encoder.Emit(OpCodes.Ret);
  158.         }
  159.         private static readonly OpCode[] Ldargs = new[] { OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3 };
  160.         private static void ConfigureLdarg(ILGenerator encoder, int index)
  161.         {
  162.             if (index >= Ldargs.Length) encoder.Emit(OpCodes.Ldarg_S, (short)(index));
  163.             else encoder.Emit(Ldargs[index]);
  164.         }
  165.         private static readonly OpCode[] Ldcs = new[] { OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8 };
  166.         private static void ConfigureLdc(ILGenerator encoder, int index)
  167.         {
  168.             if (index >= Ldcs.Length) encoder.Emit(OpCodes.Ldc_I4, index);
  169.             else encoder.Emit(Ldcs[index]);
  170.         }
  171.         #endregion
  172.     }
(This is a huge blob of code I'll explain and break down later)

Let's focus on the example at hand. I want to use this class now to generate proxies for services on the fly. To keep this dynamic I will want to extend the Proxy<T> base class into something like this:

  1.     public class ServiceLogger<T> : Proxy<T> where T : class
  2.     {
  3.         public T Service { get { return Instance; } }
  4.         protected ILogger Logger { getprivate set; }
  5.         public ServiceLogger(T service, ILogger logger) : base(service)
  6.         {
  7.             Logger = logger;
  8.         }
  9.         protected override object OnCall(MethodInfo method, object[] parameters)
  10.         {
  11.             try {
  12.                 return base.OnCall(method, parameters);
  13.             } catch (Exception ex) {
  14.                 Logger.Fatal("{0}.{1} failed : with {2}",
  15.                     method.DeclaringType.Name,
  16.                     method.Name,
  17.                     string.Join(", ", parameters));
  18.                 throw;
  19.             }
  20.         }
  21.         protected override void AfterCall(MethodInfo method, object[] parameters, object result)
  22.         {
  23.             Logger.Info("{0}.{1} called : with {2} : returning {3}",
  24.                 method.DeclaringType.Name,
  25.                 method.Name,
  26.                 string.Join(", ", parameters),
  27.                 result);
  28.         }
  29.     }
Now wherever I am instantiating a service I will simply wrap it with a ServiceLogger<T> and use the ServiceLogger<T>.Service in place of my service. Any code with a dependency on a service of type T will not know the difference between the proxy and the real service and so will not have to be altered.

Consider this:

  1.             ILogger logging;
  2.             IMyService myServiceInstance = new MyService();
  3.             ServiceLogger<IMyService> proxyLogger = new ServiceLogger<IMyService>(myServiceInstance, logging);
  4.             IDependentCode willBeLogged = new DependentCode(proxyLogger.Service);
  5.             IDependentCode wontBeLogged = new DependentCode(myServiceInstance);

The "willBeLogged" and the "wontBeLogged" objects both have the same dependency satisfied. One with a proxy, and the other with the direct service. The one with the proxy will have all of its calls to the proxy logged without having to change anything in the "DependentCode" class.

How it works:

All the code you write in .NET eventually becomes IL (Intermediate Language) when it is compiled. Even though you've written your application in C#, your application is "thinking" in IL (well, not really). Therefore you can't directly tell your application to just run a block of dynamically generated C# code at run-time without some kind of other service to compile your code down into IL. Fortunately for you there is no need to seek the services of a compiler because the .NET framework provides you with the ability to write your own IL.

The first thing you'll need to do in order to start building your own dynamic types is create a dynamic assembly in which to store it. As it is, all of the assemblies in your application are loaded into memory from DLLs and the like. Once an assembly is loaded into your AppDomain you will no longer be able to edit it. So you need to expect that each dynamic Type that you want to create will need to be housed in its own dynamic assembly.

Because of this, if you are not careful, you will have a memory leak as you create more and more dynamic types and will need to make sure that you are only creating new types when necessary. For this I have added a static field to the base Proxy<T> class that holds the dynamic type which is then generated in the static constructor. Normally I avoid adding static fields to generic classes because a new value is generated for each "T" used, however in this case it is exactly what I want.

  1.     public abstract class Proxy<T> where T : class
  2.     {
  3.         protected static readonly Type InstanceType;
  4.         ...
  5.         static Proxy() {
  6.             ...
  7.             InstanceType = Generate();
  8.         }
Now in the instance constructor I simply construct an instance of the type referenced by the static InstanceType field. In a single-threaded environment this will be enough to prevent any leaks. (maybe also in multi-threaded environments?)

The "Generate()" method will create a dynamic type that inherits the "T" interface. The dynamic type will be given a private _proxy field that will reference the owning "Proxy<T>" instance. Each method on the dynamic class will then call the "_proxy.Trigger(...)" method with the appropriate information. The "Trigger" method will call the "BeforeCall", "OnCall", and "AfterCall" virtual methods which can then be overridden by your own subclass to perform whatever interception actions you want.

When generation is complete the dynamically created type resembles a class that would look something like this:

  1.     public class GeneratedProxyClass : IFace
  2.     {
  3.         private Proxy<IFace> _proxy;
  4.         public GeneratedProxyClass(Proxy<IFace> proxy) { _proxy = proxy; }
  5.         public string MethodA(int arg1, int arg2)
  6.         {
  7.             var methodInfo = Proxy<IFace>.Lookup(1);
  8.             var parameters = new object[] { arg1, arg2 };
  9.             return (string)_proxy.Trigger(methodInfo, parameters);
  10.         }
  11.         public void MethodB(string value)
  12.         {
  13.             var methodInfo = Proxy<IFace>.Lookup(2);
  14.             var parameters = new object[] { value };
  15.             _proxy.Trigger(methodInfo, parameters);
  16.         }
  17.     }
The rest is pretty straight-forward even though it doesn't seem so. If you take a peek into the "Generate" method you will see the following steps taken:
  • Create a dynamic Assembly in the current AppDomain
  • Create a dynamic Module in the dynamic Assembly
  • Create a dynamic Type in the dynamic Module
  • Create Fields, Properties, and Methods in the dynamic Type
  • "Build" the new System.Type
Defining a member on a dynamic type is as easy as ".DefineField", ".DefineMethod", or ".DefineProperty". Obviously underlying methods won't magically know what to do when they are invoked. This is where all the IL encoding comes in.

I'm not, however,  going to dive into how to write functionality with IL in this post. My own understanding of how to write IL is very basic and most of that has come from compiling assemblies and looking at what the compiler generates. Nevertheless, I have made sure to leave comments on each Emit statement to help explain what's happening. You can see these in the "ConfigureCall" method.

Enjoy!



Did you like this post?
Let me know:   http://markonthenet.com/contact.htm

No comments:

Post a Comment