Other Posts in AOP

  1. Creating an AOP Library in .Net - Part 1
  2. Creating an AOP Library in .Net - Part 2
  3. Creating an AOP Library in .Net - Part 3
  4. Creating an AOP Library in .Net - Part 4

Creating an AOP Library in .Net - Part 3

7/13/2010
This post builds on the last post. The main goal that we will be focusing on in this post is setting up the points at which we want to weave our aspects into the main bit of code. I've already decided that I want to be at three distinct points, at the beginning of the method, the end, and when an exception occurs. However there are many different ways that I can take to let the aspects tie into these spots.
 
So let's start by talking about a couple different approaches that we could take. The first approach is what things like AspectJ use. Basically I'd design my own pseudo language and then add the compiled/IL version of that language into the locations I specified. Not fun and to be honest a bit of overkill for my needs (plus it gets us away from run time and more towards compile time subclassing). The next approach involves IL injection by the individual aspect. To be honest, this isn't a bad approach but it does mean that if you want to write an aspect you have to know IL... But for now, let's explore this:
 
   1: namespace Aspectus
   2: {
   3:     public class Manager:Singleton<Manager>
   4:     {
   5:         protected Manager()
   6:             : base()
   7:         {
   8:             Configuration = Gestalt.Manager.Instance.GetConfigFile<Configuration>("AspectusConfiguration");
   9:             Classes = new Dictionary<Type, Type>();
  10:             AssemblyName Name = new AssemblyName(Configuration.AssemblyName);
  11:             AppDomain Domain = Thread.GetDomain();
  12:             Builder = Domain.DefineDynamicAssembly(Name, AssemblyBuilderAccess.RunAndSave, Configuration.AssemblyDirectory);
  13:             Module = Builder.DefineDynamicModule(Configuration.AssemblyName, Configuration.AssemblyName + ".dll", true);
  14:         }
  15:  
  16:         public void Save()
  17:         {
  18:             Builder.Save(Configuration.AssemblyName + ".dll");
  19:         }
  20:  
  21:         public void Setup(Type Type)
  22:         {
  23:             List<Type> Interfaces=new List<Type>();
  24:             TypeBuilder TypeBuilder = Module.DefineType(Configuration.AssemblyName + "." + Type.Name + "Derived", TypeAttributes.Public, Type, Interfaces.ToArray());
  25:             MethodAttributes GetSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
  26:  
  27:             foreach (MethodInfo Method in Type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
  28:             {
  29:                 if (Method.IsVirtual)
  30:                 {
  31:                     CreateMethod(Type, Method, TypeBuilder);
  32:                 }
  33:             }
  34:  
  35:             Classes.Add(Type, TypeBuilder.CreateType());
  36:         }
  37:  
  38:         public T Create<T>()
  39:         {
  40:             T ReturnObject = (T)Classes[typeof(T)].Assembly.CreateInstance(Classes[typeof(T)].FullName);
  41:             return ReturnObject;
  42:         }
  43:  
  44:         private void CreateMethod(Type Type, MethodInfo Method, TypeBuilder TypeBuilder)
  45:         {
  46:             MethodAttributes MethodAttribute = MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Public;
  47:             if (Method.IsStatic)
  48:                 MethodAttribute |= MethodAttributes.Static;
  49:             ParameterInfo[] Parameters = Method.GetParameters();
  50:             Type[] ParameterTypes = new Type[Parameters.Length];
  51:             int x = 0;
  52:             foreach (ParameterInfo Parameter in Parameters)
  53:             {
  54:                 ParameterTypes[x] = Parameter.ParameterType;
  55:                 ++x;
  56:             }
  57:             MethodBuilder TempMethodBuilder = TypeBuilder.DefineMethod(Method.Name, MethodAttribute, Method.ReturnType, ParameterTypes);
  58:             ILGenerator MethodILGenerator = TempMethodBuilder.GetILGenerator();
  59:             LocalBuilder LocalReturn = null;
  60:             if (Method.ReturnType != typeof(void))
  61:             {
  62:                 LocalReturn = MethodILGenerator.DeclareLocal(Method.ReturnType);
  63:             }
  64:             Label MethodTryEndLabel = MethodILGenerator.DefineLabel();
  65:             MethodILGenerator.BeginExceptionBlock();
  66:             foreach (IAspect Extension in Extensions)
  67:             {
  68:                 Extension.SetupStartMethod(MethodILGenerator, Method, Type);
  69:             }
  70:             MethodILGenerator.Emit(OpCodes.Ldarg_0);
  71:             foreach (ParameterInfo Parameter in Parameters)
  72:             {
  73:                 MethodILGenerator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
  74:             }
  75:             MethodILGenerator.EmitCall(OpCodes.Call, Method, new Type[0]);
  76:             if (Method.ReturnType != typeof(void))
  77:             {
  78:                 MethodILGenerator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
  79:             }
  80:             MethodILGenerator.Emit(OpCodes.Br_S, MethodTryEndLabel);
  81:             MethodILGenerator.BeginCatchBlock(typeof(System.Exception));
  82:             foreach (IAspect Extension in Extensions)
  83:             {
  84:                 Extension.SetupExceptionMethod(MethodILGenerator, MethodInfo, Type);
  85:             }
  86:             MethodILGenerator.Emit(OpCodes.Rethrow);
  87:             MethodILGenerator.EndExceptionBlock();
  88:  
  89:             MethodILGenerator.MarkLabel(MethodTryEndLabel);
  90:  
  91:             foreach (IAspect Extension in Extensions)
  92:             {
  93:                 Extension.SetupEndMethod(MethodILGenerator, Method, Type, LocalReturn);
  94:             }
  95:  
  96:             if (Method.ReturnType != typeof(void))
  97:             {
  98:                 MethodILGenerator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
  99:             }
 100:             MethodILGenerator.Emit(OpCodes.Ret);
 101:             TypeBuilder.DefineMethodOverride(TempMethodBuilder, Method);
 102:         }
 103:  
 104:         protected AssemblyBuilder Builder { get; set; }
 105:         protected ModuleBuilder Module { get; set; }
 106:         protected Dictionary<Type, Type> Classes { get; set; }
 107:         protected Configuration Configuration { get; set; }
 108:     }
 109: }
 
The above is another version of our manager class. Not much has changed with the exception of the CreateMethod function. This function now actually overrides the individual method. It starts off by defining a new method. It has the same name as the one we're trying to override, same parameter types, return type, etc. We then get the ILGenerator for that method and start filling in the body of the function. We start off by creating a local value for our return type (if the function isn't void) and defining a label (MethodTryEndLabel). Once that is done we start a try/catch block (BeginExceptionBlock). After that, we let each extension fill in their IL (note that we will actually deal with loading our extensions later, so don't worry that this wont compile).
 
Once that is done we load up the object and all of the parameters and actually call the base method. When it returns, we store the value (if it returns a value) in the local variable we set up earlier. We then tell it to jump to the label that we defined earlier. The label hasn't been set yet so at this point it would just fail stating that you have an invalid app. After this we start our catch block, letting the extensions/aspects insert the code that they need, and then rethrowing the exception. Note that this will only be run if there is indeed an exception, otherwise we would have jumped down to our label which we set after the catch block. At this point we load our saved return value and call return. That's it for our IL, but we still have to actually override the method which is done with the DefineMethodOverride call. And after that we're done.
 
There are a couple questions you may have when reading the code at this point. First, why are we only overriding the methods? We have properties that we may wish to log, etc. Well a property is really nothing more than 2 methods, a get and a set method. By only overriding the methods, we're still overriding the get and set methods for each property (assuming the property is virtual). The next question, if you looked carefully at the code, would probably be, why are you adding one to the parameter's position if the method is not static? Well in IL generation, all non static methods have their first parameter as being this (as in this.MyProperty). In static functions, this is not the case. Sadly the property's Position property does not take this into account, so you have to add one when dealing with non static methods.
 
Anyway, at this point we have a main system that will allow us to splice in our aspects into our main code. Pretty simple, but it makes things more difficult to someone who wants to actually write an aspect of their own (IL generation is simple once you learn it but a pain to learn). Next time I'll show you a much simpler approach that actually isn't too difficult to get off the ground, in terms of the manager, but makes life MUCH simpler when it comes to the aspect writing portion. For now take a look, leave feedback, and happy coding.


Comments

Ying
July 15, 2010 3:21 AM

Thanks for your prompt response.Although I am new to AOP, I have just published one AOP related software proposal-Assembler: .Net assembly weaver based on existing assemblies at run time and design time, at http://darwincloudclient.blogspot.com/2010/07/assembler-net-assembly-weaver-based-on.html .Just for your information and in case some one really wants to make an AOP product.Ying

James Craig
July 14, 2010 8:19 AM

The idea with most of my posts isn't to give you 100% the best solution. Any series/post that I do is to show that you can easily build these things yourself (hell, in my image manipulation posts, I intentionally use slower code so that it's more readable). The point of the posts, whether it's an ORM, an AOP library, etc. is that they're all pretty simple and even a programmer with minimal skills like myself can get one up and running.As far as practical use, I wouldn't use the above code (in fact I'm not). My next post on the subject will show what I actually ended up using and it works rather well. That being said, it's not as mature/feature rich as something like PostSharp and never will be. But like I said, the idea for these posts isn't "Hey, this is the way to do it" but instead "Hey, I was bored and created this. Maybe this will inspire you to create something better."I will say that for all of my projects, I do end up releasing the source code, packa

Ying
July 14, 2010 4:31 AM

Great!Although I am new to AOP, your articles show your idea clearly.But I am wondering whether your code is practical to be used. In other words, is your code just another prototype?I don't intend to challenge you. First, there are a few AOP tools, which discontinued for years. At the same time, PostSharp could feed a company.Thank you for your great work.Ying