Using Unsafe Code for Faster Image Manipulation

Over the past couple months, I've had a lot of traffic (well a lot for me anyway) going toward my various image editing posts. While the general concepts are there, I get a decent number of people complaining that they're a bit slow. They are slow when the image gets to be rather large. The reason for this is because I intentionally use two functions that are incredibly slow, GetPixel and SetPixel... These functions have to figure out the format that the image is actually displayed in, and give us/set the individual pixel in the image. To say the least that's a slow process (safe but slow). So why would I use them if they're so slow? Because they're easy to understand. Most of my code was designed such that someone who didn't know much about images or maybe even that much about C# could see what was going on and get something together for their needs. The alternative gets much more complicated, much faster...

That alternative is by using unsafe code. I can honestly say that there are only a couple times that I've ever seen someone use unsafe code (usually when doing image manipulation). So if you're a C# developer, there is a good chance that you've never (or at least rarely) seen the keyword used. So what the heck is the unsafe keyword? The unsafe keyword is used to define a section of code that uses pointers.  You can define it on a function, encapsulate a small bit of code with it, etc. You're just saying this bit of code uses pointers. If you've done C++ programming, you've dealt with pointers enough that this should be rather simple. For everyone else, well I'm not going to explain pointers that much other than to say that they point to things in memory.

OK, well maybe a little bit more is needed. In our case, think of our image as an array of bytes in memory. When we create a pointer and point it to our image, it does not hold our image. The pointer is simply an int (actually it depends on the language/architecture as to what the type is) that says we can find this image at location X in memory. On top of this we can change where the pointer points. For instance if we add 1 to it, we get the next byte in memory (1 pixel in on our image, assuming 1 pixel=1 byte). It's referred to as pointer arithmetic. That's all you need to know for our purposes, but If you want to know more Google is your friend.

Getting back to the unsafe keyword, the advantages of using pointers and unsafe code is it gets rid of a lot of bounds checks, etc. that slow down your code. The downside is that it can introduce potential security flaws if it's not written correctly... Not to mention they tend to increase the number of bugs that come about (especially with people who have limited experience with them). That being said, we're going to use unsafe code to speed up our image manipulation.

The two functions that were slowing us down were GetPixel and SetPixel. So let's create our own. So how do we do this? Well with images there are a couple steps that you need to do to get at the data so that we can set/get a pixel. The first step (after loading the image) is to lock it. I've written a function to do this step for me:

 

        internal static BitmapData LockImage(Bitmap Image)

        {

            try

            {

                return Image.LockBits(new Rectangle(0, 0, Image.Width, Image.Height),

                    ImageLockMode.ReadWrite, Image.PixelFormat);

            }

            catch { throw; }

        }

With this function we're telling the image to lock all of it (since the rectangle is the entire image) for both reading and writing. The last item is the pixel format of the image. In our case we're using the image's own pixel format, because we don't exactly know what the format is suppose to be. That being said, in later code we're going to limit ourselves to 24 and 32 bit images (expanding this for 48 or 64 bit is actually fairly simple but not something that I needed). In return the function returns a BitmapData class. This class contains our actual bits of data, but we still aren't quite ready to edit it yet. The next step is we need to figure out the actual size of each pixel (luckily for us, they tell us):

 

        internal static int GetPixelSize(BitmapData Data)

        {

            if (Data.PixelFormat == PixelFormat.Format24bppRgb)

                return 3;

            else if (Data.PixelFormat == PixelFormat.Format32bppArgb 

                || Data.PixelFormat == PixelFormat.Format32bppPArgb 

                || Data.PixelFormat == PixelFormat.Format32bppRgb)

                return 4;

            return 0;

        }

As I said earlier, we're only really going to handle 24 and 32 bit images. Once we have our data and know our pixel size, we can actually get/set our needed pixel:

 

        internal static unsafe Color GetPixel(BitmapData Data, int x, int y,int PixelSizeInBytes)

        {

            try

            {

                byte* DataPointer = (byte*)Data.Scan0;

                DataPointer = DataPointer + (y * Data.Stride) + (x * PixelSizeInBytes);

                if (PixelSizeInBytes == 3)

                {

                    return Color.FromArgb(DataPointer[2], DataPointer[1], DataPointer[0]);

                }

                return Color.FromArgb(DataPointer[3], DataPointer[2], DataPointer[1], DataPointer[0]);

            }

            catch { throw; }

        }

 

        internal static unsafe void SetPixel(BitmapData Data, int x, int y,Color PixelColor,int PixelSizeInBytes)

        {

            try

            {

                byte* DataPointer = (byte*)Data.Scan0;

                DataPointer = DataPointer + (y * Data.Stride) + (x * PixelSizeInBytes);

                if (PixelSizeInBytes == 3)

                {

                    DataPointer[2] = PixelColor.R;

                    DataPointer[1] = PixelColor.G;

                    DataPointer[0] = PixelColor.B;

                    return;

                }

                DataPointer[3] = PixelColor.A;

                DataPointer[2] = PixelColor.R;

                DataPointer[1] = PixelColor.G;

                DataPointer[0] = PixelColor.B;

            }

            catch { throw; }

        }

That code gets/sets our individual pixels respectively. In it you'll notice that we finally use the unsafe keyword. Up until now we didn't use any pointers, but here we're using them to point to the actual data. The BitmapData class contains an IntPtr called Scan0. This is the actual data of our image... Well the pointer to our actual data. The Stride property tells us the actual width of the image. You see, if we have an image that is 207 pixels wide, it might not actually be 207 pixels wide. Images, to make them faster to manipulate and load, tend to have actual widths that are multiple of 4s (although with 64 bit processors, I wonder if it would be a multiple of 8...). So in reality the image would request enough memory such that the width was 208. The extra pixel would just be sitting out there, not doing anything. And in normal managed code, it wouldn't matter, but in unsafe pointer land we need to know about it. So using some pointer arithmetic (y*ActualWidthInBytes+x*PixelSizeInBytes), we can get the actual location of the pixel that we want. From here, you'll notice that we're accessing it in reverse order (BGR instead of RGB). I don't know why this is done in memory, but I do remember that bitmaps are actually stored in reverse order. The bottom right pixel is the first one that you come to, stored in BGR order, and you end with the upper left (no idea why this is the case though). Anyway, we get our R, G, B, and potentially A values and create a Color object and return it.

The last thing that we have to do, once we're done manipulating our image, is unlock the Bitmap object. Once again I've built a function to help with that:

 

        internal static void UnlockImage(Bitmap Image,BitmapData ImageData)

        {

            try

            {

                Image.UnlockBits(ImageData);

            }

            catch { throw; }

        }

 

That's all there is to it really. Once unlocked, we're done. We can simply replace our functions from before with these and we end up with a much faster system. For instance we can do a median filter like this:

 

        public static Bitmap MedianFilter(Bitmap OriginalImage, int Size)

        {

            try

            {

                System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(OriginalImage.Width, OriginalImage.Height);

                BitmapData NewData = Image.LockImage(NewBitmap);

                BitmapData OldData = Image.LockImage(OriginalImage);

                int NewPixelSize = Image.GetPixelSize(NewData);

                int OldPixelSize = Image.GetPixelSize(OldData);

                Random.Random TempRandom = new Random.Random();

                int ApetureMin = -(Size / 2);

                int ApetureMax = (Size / 2);

                for (int x = 0; x < NewBitmap.Width; ++x)

                {

                    for (int y = 0; y < NewBitmap.Height; ++y)

                    {

                        List<int> RValues = new List<int>();

                        List<int> GValues = new List<int>();

                        List<int> BValues = new List<int>();

                        for (int x2 = ApetureMin; x2 < ApetureMax; ++x2)

                        {

                            int TempX = x + x2;

                            if (TempX >= 0 && TempX < NewBitmap.Width)

                            {

                                for (int y2 = ApetureMin; y2 < ApetureMax; ++y2)

                                {

                                    int TempY = y + y2;

                                    if (TempY >= 0 && TempY < NewBitmap.Height)

                                    {

                                        Color TempColor = Image.GetPixel(OldData, TempX, TempY, OldPixelSize);

                                        RValues.Add(TempColor.R);

                                        GValues.Add(TempColor.G);

                                        BValues.Add(TempColor.B);

                                    }

                                }

                            }

                        }

                        Color MedianPixel = Color.FromArgb(Math.MathHelper.Median<int>(RValues),

                            Math.MathHelper.Median<int>(GValues),

                            Math.MathHelper.Median<int>(BValues));

                        Image.SetPixel(NewData, x, y, MedianPixel, NewPixelSize);

                    }

                }

                Image.UnlockImage(NewBitmap, NewData);

                Image.UnlockImage(OriginalImage, OldData);

                return NewBitmap;

            }

            catch { throw; }

        }

The Math.MathHelper.Median function simply sorts the list and gets the middle item. But it's not that different from the code before (although a bit cleaned up). The speed difference though is rather amazing. For a small 274x350 image we go from 8 seconds to about 2 seconds. And that's with a simple update. If you wanted, you could increase this further by breaking out the GetPixel and SetPixel functions and doing the pointer arithmetic in the function itself (doing 1 add and 1 assignment instead of 2 multiplications, 2 adds, and an assignment and Color object creation makes things run a bit faster). But still, for a simple update that's a big speed increase.

So try it out. Copy it into your IDE and compile it (you may want to set up a function call, load up an image, save it, etc. so it's at least a somewhat interesting test), I'll wait... Oh, it failed didn't it? The reason for this is because you must compile unsafe code using /unsafe (just go into the properties of the project, build tab, and check the Allow unsafe code box). OK, now try compiling it and you should see a nice, much faster, median filter applied to your image. So try it out, leave feedback, and happy coding.

 

 

 

 

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 8/20/2010 at 8:34 AM
Tags: , , ,
Categories: C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Aspectus, a New AOP Library, Released

For the past couple of weeks, I've made posts about the basics of making an AOP library. Well today I'm releasing the finished product to the world. Whether or not you want it is up to you though. In the release you'll find documentation, the source code, the compiled versions of the library, and some example aspects/extensions. As always I'm giving it away under the MIT license, so feel free to use it however you want. Anyway, go over to the library's site over on CodePlex and take a look.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 7/26/2010 at 12:07 PM
Tags: , ,
Categories: C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Creating an AOP Library in .Net - Part 4

When I last left off, I had a basic Manager class that went through our aspects and allowed them to inject their own IL into the sub class at various join points. To be completely honest, doing it that way, while easy to set up, is annoying at best to anyone who wants to actually write an aspect. Instead I would rather write my aspect in C# or VB and not have to worry about IL at all. So I ended up taking another approach, interface injection.

By the way, before I go on, I should let you know that I tend to use various terms the way that I remember/think of things. So if you hear interface injection and think of Martin Fowler's version of that phrase, I'm not talking about that. Well not exactly anyway. When I say interface injection, I mean that at run time we add an interface to an already existing class. That's it. Has nothing really to do with IoC containers, etc. And actually since we're using sub classing, we're not adding it to the class directly but instead to the sub class and implementing it.

Basically I want each aspect, if they so choose, to be able to add their own interface/implementation to the sub class. Now this would require IL generation, but once implemented it could be accessed using normal code from anywhere, including other aspects. Using this technique, I had the manager implement one interface that contained events for when the function started, ended, or when an error occurred. Thus most aspects would simply be able to tie into these events in order to accomplish what they wanted and the interface injection would be a special case. So let's look at some code:


public class Manager:Singleton<Manager>
    {
        protected Manager()
            : base()
        {
            Configuration = Gestalt.Manager.Instance.GetConfigFile<Configuration>("AspectusConfiguration");
            if (Configuration.AspectLocation.EndsWith(".dll",StringComparison.CurrentCultureIgnoreCase))
            {
                Assembly TempAssembly = Assembly.LoadFile(Configuration.AspectLocation);
                Gestalt.Manager.Instance.RegisterConfigFile(TempAssembly);
                DotExtension.Manager.Instance.Setup(TempAssembly);
            }
            else if (Utilities.IO.FileManager.DirectoryExists(Configuration.AspectLocation))
            {
                List<Assembly> Assemblies = Utilities.Reflection.Reflection.GetAssembliesFromDirectory(Configuration.AspectLocation, true);
                foreach (Assembly Assembly in Assemblies)
                {
                    Gestalt.Manager.Instance.RegisterConfigFile(Assembly);
                    DotExtension.Manager.Instance.Setup(Assembly);
                }
            }
            else
            {
                Assembly TempAssembly = Assembly.Load(Configuration.AspectLocation);
                Gestalt.Manager.Instance.RegisterConfigFile(TempAssembly);
                DotExtension.Manager.Instance.Setup(TempAssembly);
            }
            Classes = new Dictionary<Type, Type>();
            AssemblyName Name = new AssemblyName(Configuration.AssemblyName);
            AppDomain Domain = Thread.GetDomain();
            Builder = Domain.DefineDynamicAssembly(Name, AssemblyBuilderAccess.RunAndSave, Configuration.AssemblyDirectory);
            Module = Builder.DefineDynamicModule(Configuration.AssemblyName, Configuration.AssemblyName + ".dll", true);
        }

        public void Save()
        {
            Builder.Save(Configuration.AssemblyName + ".dll");
        }

        public void Setup(Type Type)
        {
            List<Type> Interfaces = new List<Type>();
            foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
            {
                if (Extension.InterfacesUsing != null)
                {
                    Interfaces.AddRange(Extension.InterfacesUsing);
                }
            }
            Interfaces.Add(typeof(IEvents));
            TypeBuilder TypeBuilder = Module.DefineType(Configuration.AssemblyName + "." + Type.Name + "Derived", TypeAttributes.Public, Type, Interfaces.ToArray());
            MethodAttributes GetSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
            CreateStartingEvent(TypeBuilder, GetSetAttributes);
            CreateEndingEvent(TypeBuilder, GetSetAttributes);
            CreateExceptionEvent(TypeBuilder, GetSetAttributes);

            foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
            {
                Extension.SetupInterfaces(TypeBuilder);
            }

            foreach (MethodInfo Method in Type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
            {
                if (Method.IsVirtual)
                {
                    CreateMethod(Type, Method, TypeBuilder);
                }
            }

            Classes.Add(Type, TypeBuilder.CreateType());
        }

        public T Create<T>()
        {
            T ReturnObject = (T)Classes[typeof(T)].Assembly.CreateInstance(Classes[typeof(T)].FullName);
            foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
            {
                Extension.Setup(ReturnObject);
            }
            return ReturnObject;
        }

        private void CreateMethod(Type Type, MethodInfo Method, TypeBuilder TypeBuilder)
        {
            MethodAttributes MethodAttribute = MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Public;
            if (Method.IsStatic)
                MethodAttribute |= MethodAttributes.Static;
            ParameterInfo[] Parameters = Method.GetParameters();
            Type[] ParameterTypes = new Type[Parameters.Length];
            int x = 0;
            foreach (ParameterInfo Parameter in Parameters)
            {
                ParameterTypes[x] = Parameter.ParameterType;
                ++x;
            }
            MethodBuilder TempMethodBuilder = TypeBuilder.DefineMethod(Method.Name, MethodAttribute, Method.ReturnType, ParameterTypes);
            ILGenerator MethodILGenerator = TempMethodBuilder.GetILGenerator();
            LocalBuilder LocalReturn = null;
            if (Method.ReturnType != typeof(void))
            {
                LocalReturn = MethodILGenerator.DeclareLocal(Method.ReturnType);
            }
            LocalBuilder StartingArgs = MethodILGenerator.DeclareLocal(typeof(Starting));
            LocalBuilder EndingArgs = MethodILGenerator.DeclareLocal(typeof(Ending));
            LocalBuilder ErrorArgs = MethodILGenerator.DeclareLocal(typeof(Aspectus.EventArgs.Exception));
            Label MethodTryEndLabel = MethodILGenerator.DefineLabel();
            MethodILGenerator.BeginExceptionBlock();
            AddStartingBlock(MethodILGenerator, StartingArgs, Method, LocalReturn);
            foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
            {
                Extension.SetupStartMethod(MethodILGenerator, Method, Type);
            }
            if (!Method.IsStatic)
            {
                MethodILGenerator.Emit(OpCodes.Ldarg_0);
            }
            foreach (ParameterInfo Parameter in Parameters)
            {
                MethodILGenerator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
            }
            MethodILGenerator.EmitCall(OpCodes.Call, Method, new Type[0]);
            if (Method.ReturnType != typeof(void))
            {
                MethodILGenerator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
            }
            MethodILGenerator.Emit(OpCodes.Br_S, MethodTryEndLabel);
            AddExceptionBlock(MethodILGenerator, Method, Type, ErrorArgs);

            MethodILGenerator.MarkLabel(MethodTryEndLabel);

            foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
            {
                Extension.SetupEndMethod(MethodILGenerator, Method, Type, LocalReturn);
            }

            AddEndingBlock(MethodILGenerator, EndingArgs, Method, LocalReturn);
            if (Method.ReturnType != typeof(void))
            {
                MethodILGenerator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
            }
            MethodILGenerator.Emit(OpCodes.Ret);
            TypeBuilder.DefineMethodOverride(TempMethodBuilder, Method);
        }

        private void AddEndingBlock(ILGenerator Generator, LocalBuilder EndingArgs, MethodInfo Method,LocalBuilder LocalReturn)
        {
            Generator.Emit(OpCodes.Newobj, typeof(Ending).GetConstructor(new Type[0]));
            Generator.Emit(OpCodes.Stloc, EndingArgs.LocalIndex);
            Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
            Generator.Emit(OpCodes.Ldstr, Method.Name);
            Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("MethodName").GetSetMethod(), null);
            if (LocalReturn != null)
            {
                Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
                Generator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
                Generator.Emit(OpCodes.Box, LocalReturn.LocalType);
                Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("ReturnValue").GetSetMethod(), null);
            }
            ParameterInfo[] Parameters = Method.GetParameters();
            foreach (ParameterInfo Parameter in Parameters)
            {
                Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
                Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("Parameters").GetGetMethod(), null);
                Generator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
                if (!Parameter.ParameterType.IsByRef)
                {
                    Generator.Emit(OpCodes.Box, Parameter.ParameterType);
                }
                else
                {
                    Generator.Emit(OpCodes.Box, typeof(int));
                }
                Generator.EmitCall(OpCodes.Callvirt, typeof(List<object>).GetMethod("Add"), null);
            }
            Generator.Emit(OpCodes.Nop);
            Generator.Emit(OpCodes.Ldarg_0);
            Generator.Emit(OpCodes.Castclass, typeof(IEvents));
            Generator.EmitCall(OpCodes.Callvirt, typeof(IEvents).GetProperty("Aspectus_Ending").GetGetMethod(), null);
            Generator.Emit(OpCodes.Ldarg_0);
            Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
            Type EventHelperType = typeof(Utilities.Events.EventHelper);
            MethodInfo[] Methods = EventHelperType.GetMethods();
            MethodInfo TempMethod = null;
            foreach (MethodInfo TempMethodInfo in Methods)
            {
                if (TempMethodInfo.GetParameters().Length == 3)
                {
                    TempMethod = TempMethodInfo;
                    break;
                }
            }
            TempMethod = TempMethod.MakeGenericMethod(new Type[] { typeof(Ending) });
            Generator.EmitCall(OpCodes.Call, TempMethod, null);
            if (LocalReturn != null)
            {
                Label Equal = Generator.DefineLabel();
                Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
                Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("ReturnValue").GetGetMethod(), null);
                Generator.Emit(OpCodes.Ldnull);
                Generator.Emit(OpCodes.Ceq);
                Generator.Emit(OpCodes.Brtrue_S, Equal);
                Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
                Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("ReturnValue").GetGetMethod(), null);
                Generator.Emit(OpCodes.Unbox_Any, LocalReturn.LocalType);
                Generator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
                Generator.MarkLabel(Equal);
            }
        }

        private void AddExceptionBlock(ILGenerator Generator, MethodInfo MethodInfo, Type Type, LocalBuilder ErrorArgs)
        {
            Generator.BeginCatchBlock(typeof(System.Exception));

            Generator.Emit(OpCodes.Stloc_0);
            Generator.Emit(OpCodes.Newobj, typeof(Aspectus.EventArgs.Exception).GetConstructor(new Type[0]));
            Generator.Emit(OpCodes.Stloc, ErrorArgs.LocalIndex);
            Generator.Emit(OpCodes.Ldloc, ErrorArgs.LocalIndex);
            Generator.Emit(OpCodes.Ldloc_0);
            Generator.EmitCall(OpCodes.Callvirt, typeof(Aspectus.EventArgs.Exception).GetProperty("InternalException").GetSetMethod(), null);
            Generator.Emit(OpCodes.Nop);
            Generator.Emit(OpCodes.Ldarg_0);
            Generator.Emit(OpCodes.Castclass, typeof(IEvents));
            Generator.EmitCall(OpCodes.Callvirt, typeof(IEvents).GetProperty("Aspectus_Exception").GetGetMethod(), null);
            Generator.Emit(OpCodes.Ldarg_0);
            Generator.Emit(OpCodes.Ldloc, ErrorArgs.LocalIndex);
            Type EventHelperType = typeof(Utilities.Events.EventHelper);
            MethodInfo[] Methods = EventHelperType.GetMethods();
            MethodInfo TempMethod = null;
            foreach (MethodInfo Method in Methods)
            {
                if (Method.GetParameters().Length == 3)
                {
                    TempMethod = Method;
                    break;
                }
            }
            TempMethod = TempMethod.MakeGenericMethod(new Type[] { typeof(Aspectus.EventArgs.Exception) });
            Generator.EmitCall(OpCodes.Call, TempMethod, null);
            foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
            {
                Extension.SetupExceptionMethod(Generator, MethodInfo, Type);
            }
            Generator.Emit(OpCodes.Rethrow);
            Generator.EndExceptionBlock();
        }

        private void AddStartingBlock(ILGenerator Generator, LocalBuilder StartingArgs, MethodInfo Method,LocalBuilder LocalReturn)
        {
            Generator.Emit(OpCodes.Newobj, typeof(Starting).GetConstructor(new Type[0]));
            Generator.Emit(OpCodes.Stloc, StartingArgs.LocalIndex);
            Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
            Generator.Emit(OpCodes.Ldstr, Method.Name);
            Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("MethodName").GetSetMethod(), null);
            ParameterInfo[] Parameters = Method.GetParameters();
            foreach (ParameterInfo Parameter in Parameters)
            {
                    Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
                    Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("Parameters").GetGetMethod(), null);
                    Generator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
                    if (!Parameter.ParameterType.IsByRef)
                    {
                        Generator.Emit(OpCodes.Box, Parameter.ParameterType);
                    }
                    else
                    {
                        Generator.Emit(OpCodes.Box, typeof(int));
                    }
                    Generator.EmitCall(OpCodes.Callvirt, typeof(List<object>).GetMethod("Add"), null);
            }
            Generator.Emit(OpCodes.Ldarg_0);
            Generator.Emit(OpCodes.Castclass, typeof(IEvents));
            Generator.EmitCall(OpCodes.Callvirt, typeof(IEvents).GetProperty("Aspectus_Starting").GetGetMethod(), null);
            Generator.Emit(OpCodes.Ldarg_0);
            Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
            Type EventHelperType = typeof(Utilities.Events.EventHelper);
            MethodInfo[] Methods = EventHelperType.GetMethods();
            MethodInfo TempMethod = null;
            foreach (MethodInfo TempMethodInfo in Methods)
            {
                if (TempMethodInfo.GetParameters().Length == 3)
                {
                    TempMethod = TempMethodInfo;
                    break;
                }
            }
            TempMethod = TempMethod.MakeGenericMethod(new Type[] { typeof(Starting) });
            Generator.EmitCall(OpCodes.Call, TempMethod, null);
            if (LocalReturn != null)
            {
                Label Equal = Generator.DefineLabel();
                Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
                Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("ReturnValue").GetGetMethod(), null);
                Generator.Emit(OpCodes.Ldnull);
                Generator.Emit(OpCodes.Ceq);
                Generator.Emit(OpCodes.Brtrue_S, Equal);
                Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
                Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("ReturnValue").GetGetMethod(), null);
                Generator.Emit(OpCodes.Unbox_Any, LocalReturn.LocalType);
                Generator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
                Generator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
                Generator.Emit(OpCodes.Ret);
                Generator.MarkLabel(Equal);
            }
        }

        private void CreateStartingEvent(TypeBuilder TypeBuilder, MethodAttributes GetSetAttributes)
        {
            FieldBuilder FieldBuilder = TypeBuilder.DefineField("_Aspectus_Starting", typeof(EventHandler<Starting>), FieldAttributes.Private);
            PropertyBuilder PropertyBuilder = TypeBuilder.DefineProperty("Aspectus_Starting", PropertyAttributes.SpecialName,
                typeof(EventHandler<Starting>), null);
            MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_Aspectus_Starting", GetSetAttributes,
                typeof(EventHandler<Starting>), Type.EmptyTypes);
            ILGenerator GetGenerator = ValuePropertyGet.GetILGenerator();
            GetGenerator.Emit(OpCodes.Ldarg_0);
            GetGenerator.Emit(OpCodes.Ldfld, FieldBuilder);
            GetGenerator.Emit(OpCodes.Ret);
            PropertyBuilder.SetGetMethod(ValuePropertyGet);

            MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_Aspectus_Starting", GetSetAttributes, null,
                new Type[] { typeof(EventHandler<Starting>) });
            ILGenerator SetGenerator = ValuePropertySet.GetILGenerator();
            SetGenerator.Emit(OpCodes.Ldarg_0);
            SetGenerator.Emit(OpCodes.Ldarg_1);
            SetGenerator.Emit(OpCodes.Stfld, FieldBuilder);
            SetGenerator.Emit(OpCodes.Ret);
            PropertyBuilder.SetSetMethod(ValuePropertySet);
        }

        private void CreateEndingEvent(TypeBuilder TypeBuilder, MethodAttributes GetSetAttributes)
        {
            FieldBuilder FieldBuilder = TypeBuilder.DefineField("_Aspectus_Ending", typeof(EventHandler<Ending>), FieldAttributes.Private);
            PropertyBuilder PropertyBuilder = TypeBuilder.DefineProperty("Aspectus_Ending", PropertyAttributes.SpecialName,
                typeof(EventHandler<Ending>), null);
            MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_Aspectus_Ending", GetSetAttributes,
                typeof(EventHandler<Ending>), Type.EmptyTypes);
            ILGenerator GetGenerator = ValuePropertyGet.GetILGenerator();
            GetGenerator.Emit(OpCodes.Ldarg_0);
            GetGenerator.Emit(OpCodes.Ldfld, FieldBuilder);
            GetGenerator.Emit(OpCodes.Ret);
            PropertyBuilder.SetGetMethod(ValuePropertyGet);

            MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_Aspectus_Ending", GetSetAttributes, null,
                new Type[] { typeof(EventHandler<Ending>) });
            ILGenerator SetGenerator = ValuePropertySet.GetILGenerator();
            SetGenerator.Emit(OpCodes.Ldarg_0);
            SetGenerator.Emit(OpCodes.Ldarg_1);
            SetGenerator.Emit(OpCodes.Stfld, FieldBuilder);
            SetGenerator.Emit(OpCodes.Ret);
            PropertyBuilder.SetSetMethod(ValuePropertySet);
        }

        private void CreateExceptionEvent(TypeBuilder TypeBuilder, MethodAttributes GetSetAttributes)
        {
            FieldBuilder FieldBuilder = TypeBuilder.DefineField("_Aspectus_Exception", typeof(EventHandler<Aspectus.EventArgs.Exception>), FieldAttributes.Private);
            PropertyBuilder PropertyBuilder = TypeBuilder.DefineProperty("Aspectus_Exception", PropertyAttributes.SpecialName,
                typeof(EventHandler<Aspectus.EventArgs.Exception>), null);
            MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_Aspectus_Exception", GetSetAttributes,
                typeof(EventHandler<Aspectus.EventArgs.Exception>), Type.EmptyTypes);
            ILGenerator GetGenerator = ValuePropertyGet.GetILGenerator();
            GetGenerator.Emit(OpCodes.Ldarg_0);
            GetGenerator.Emit(OpCodes.Ldfld, FieldBuilder);
            GetGenerator.Emit(OpCodes.Ret);
            PropertyBuilder.SetGetMethod(ValuePropertyGet);

            MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_Aspectus_Exception", GetSetAttributes, null,
                new Type[] { typeof(EventHandler<Aspectus.EventArgs.Exception>) });
            ILGenerator SetGenerator = ValuePropertySet.GetILGenerator();
            SetGenerator.Emit(OpCodes.Ldarg_0);
            SetGenerator.Emit(OpCodes.Ldarg_1);
            SetGenerator.Emit(OpCodes.Stfld, FieldBuilder);
            SetGenerator.Emit(OpCodes.Ret);
            PropertyBuilder.SetSetMethod(ValuePropertySet);
        }

        protected AssemblyBuilder Builder { get; set; }
        protected ModuleBuilder Module { get; set; }
        protected Dictionary<Type, Type> Classes { get; set; }
        protected Configuration Configuration { get; set; }
    }

Now the code above is a bit long, but it's the finished item (well finished as of this writing). So let's go over each function. Starting with the constructor, we start by loading the configuration object using Gestalt.Net. The if statement isn't that important, just checking if it's a DLL, directory, or preloaded DLL. We create a dictionary for mapping our classes to the sub classes, and set up our module. After that is simply the Save function. We've seen most of this before, but the following function, Setup, is where things start to change.

In Setup we start by getting extensions/aspects from DotExtension (I said at the beginning that I created all of those libraries for a reason). You'll notice that it asks for any extension that implements IAspect. IAspect is our aspect's interface:


    public interface IAspect
    {
        void SetupStartMethod(ILGenerator MethodILGenerator, MethodInfo Method, Type Type);
        void SetupEndMethod(ILGenerator MethodILGenerator, MethodInfo Method, Type Type, LocalBuilder ReturnValue);
        void SetupExceptionMethod(ILGenerator MethodILGenerator, MethodInfo Method, Type Type);
        void Setup(object Object);
        void SetupInterfaces(TypeBuilder TypeBuilder);
        List<Type> InterfacesUsing { get; set; }
    }


The interface contains a couple functions and one property. At this point we're interested in the property. Any interfaces that the aspect wishes to implement it adds now. It also adds one interface called IEvents:


    public interface IEvents
    {
        #region Events

        /// <summary>
        /// Called when property/function is ending
        /// </summary>
        EventHandler<Ending> Aspectus_Ending { get; set; }

        /// <summary>
        /// Called when property/function is starting
        /// </summary>
        EventHandler<Starting> Aspectus_Starting { get; set; }

        /// <summary>
        /// Called when an error is caught
        /// </summary>
        EventHandler<Exception> Aspectus_Exception { get; set; }

        #endregion
    }

IEvents is used by the manager to let aspects interact with the sub class. After that, it creates our sub class's builder, sending in the interfaces and telling it that we will be implementing them. The next bit are three new functions, CreateStartingEvent, CreateEndingEvent, and CreateExceptionEvent. You'll notice that what these three functions do is create an event field for each point and then the get/set functions for the property. Basically we're implementing IEvents.

Once we have IEvents implemented, we let the aspects implement their own interfaces by calling SetupInterfaces on each aspect. Finally we go back to rather familiar code, we get our list of methods and if they're virtual, we override them by calling CreateMethod. Once that is done we create the sub class and add it to our dictionary.

The CreateMethod function is where we start getting a bit more complex. We start by getting a list of parameter types, create our method that will override the preexisting method, setup a return value if we need it, etc. Mostly pretty basic stuff that we did last time. However after that we create three locals, StartingArgs, EndingArgs, and ErrorArgs. These are the actual EventArgs that we will be feeding the three events that we defined earlier. I guess now would be a good time to actually see what they contain:


    public class Starting : System.EventArgs
    {
        public Starting()
            : base()
        {
            MethodName = "";
            Parameters = new List<object>();
            ReturnValue = null;
        }

        public string MethodName { get; set; }
        public List<object> Parameters { get; set; }
        public object ReturnValue { get; set; }
    }

    public class Ending : System.EventArgs
    {
        public Ending()
            : base()
        {
            MethodName = "";
            Parameters = new List<object>();
            ReturnValue = null;
        }

        public string MethodName { get; set; }
        public List<object> Parameters { get; set; }
        public object ReturnValue { get; set; }
    }

    public class Exception:System.EventArgs
    {
        public System.Exception InternalException { get; set; }
    }


You'll notice the Starting and Ending classes contain properties for the method name, a list of parameters, and the return value. These will be used to not only feed it information but also get information from the aspects. Anyway, back to CreateMethod... After the locals are defined we setup a label that is set at the end of our try/catch and start the try/catch block. The next function is AddStartingBlock. This function generates a bit of IL that initiates the StartingArgs, loads up the method name into MethodName, loads the parameters into the list, and then feeds StartingArgs to the Aspectus_Starting event. You may wish to note that loading the parameter list has two cases depending on whether or not it was passed in by reference or not. If it's by reference, we simply send it the pointer to the parameter. Otherwise we send the actual value. After the event is thrown, we check to see if there was a return value that was set by one of our aspects. If it was we stop our method and return that value. Otherwise we go back to CreateMethod.

Once back in CreateMethod, we allow the aspects to inject any IL that they need to at the start of the function. Note that this is most likely going to be for implementing the interface. We then call our base class's function, store the results in our local return value, and add our exception block. This is similar to the starting block but instead starts the catch portion of the try/catch and calls the exception event, and ultimately rethrows our exception. However if no exception occurs, we start our ending block. In this part we once again call our ending event, this time sending in our current return value along with the parameters and method name. We then check if they sent back a new value, etc. And if they did we return that instead of the original value. After all of that is done we set our new method to override our old method and we're done.

Past that we have our Create function, which is similar to our previous versions of it, except this time we allow each aspect to do any setup that they require (tie into the events, etc.). The only other bit of code that's of importance is a base class that I've yet to show you:


    public class Aspect : Extension, IAspect
    {
        public virtual void Setup(object Object)
        {
        }

        public virtual List<Type> InterfacesUsing { get; set; }

        public virtual void SetupStartMethod(System.Reflection.Emit.ILGenerator MethodILGenerator, System.Reflection.MethodInfo Method, Type Type)
        {

        }


        public virtual void SetupEndMethod(System.Reflection.Emit.ILGenerator MethodILGenerator, System.Reflection.MethodInfo Method, Type Type, System.Reflection.Emit.LocalBuilder ReturnValue)
        {
        }

        public virtual void SetupExceptionMethod(System.Reflection.Emit.ILGenerator MethodILGenerator, System.Reflection.MethodInfo Method, Type Type)
        {
        }

        public virtual void SetupInterfaces(System.Reflection.Emit.TypeBuilder TypeBuilder)
        {
        }
    }


And all this does is implements Extension from DotExtension and IAspect so any aspect that is created can use this instead of remembering that it needs both of those items. With this basic setup I can easily implement aspects to do a number of things. For instance logging:


    public class LoggingAspect : Aspect
    {
        public override void Setup(object Object)
        {
            try
            {
                ((IEvents)Object).Aspectus_Starting += Start;
                ((IEvents)Object).Aspectus_Ending += End;
                ((IEvents)Object).Aspectus_Exception += Exception;
            }
            catch { throw; }
        }

        public void Start(object sender, Starting e)
        {
            try
            {
                StringBuilder Builder = new StringBuilder();
                string Splitter = "";
                foreach (object Parameter in e.Parameters)
                {
                    Builder.Append(Splitter + Parameter);
                    Splitter = ",";
                }
                BlammoNet.Manager.Instance.GetLog().LogMessage("Entered method {0}({1})", MessageType.Debug, e.MethodName, Builder.ToString());
            }
            catch { throw; }
        }

        public void End(object sender, Ending e)
        {
            try
            {
                StringBuilder Builder = new StringBuilder();
                if (e.ReturnValue != null)
                {
                    Builder.Append(e.ReturnValue.ToString());
                }
                BlammoNet.Manager.Instance.GetLog().LogMessage("Exited method {0}: Return Value: {1}", MessageType.Debug, e.MethodName, Builder.ToString());
            }
            catch { throw; }
        }

        public void Exception(object sender, Aspectus.EventArgs.Exception e)
        {
            try
            {
                BlammoNet.Manager.Instance.GetLog().LogMessage("Exception occurred: {0}", MessageType.Error, e.InternalException.ToString());
            }
            catch { throw; }
        }
    }


If I wanted to use something like NLog or log4net, it would be about as simple, but of course I'm going to use my own logging library. Now this would log EVERY function, which could bog things down a bit so let's indicate when we want to do something by using attributes:


    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class ProfileAttribute : Attribute
    {
    }

    public class ProfilerAspect : Aspect
    {
        public ProfilerAspect()
            : base()
        {
            Profilers = new Dictionary<string, Profiler>();
        }

        public override void Setup(object Object)
        {
            try
            {
                if (Object.GetType().GetCustomAttributes(typeof(ProfileAttribute), true).Length > 0)
                {
                    ((IEvents)Object).Aspectus_Starting += Start;
                    ((IEvents)Object).Aspectus_Ending += End;
                    ((IEvents)Object).Aspectus_Exception += Exception;
                }
            }
            catch { throw; }
        }

        public void Start(object sender, Starting e)
        {
            try
            {
                if (Profilers.ContainsKey(e.MethodName))
                {
                    Profilers[e.MethodName] = new Profiler(e.MethodName);
                }
                else
                {
                    Profilers.Add(e.MethodName, new Profiler(e.MethodName));
                }
            }
            catch { throw; }
        }

        public void End(object sender, Ending e)
        {
            try
            {
                if (Profilers.ContainsKey(e.MethodName))
                {
                    Profilers[e.MethodName].Stop();
                    Profilers[e.MethodName].Dispose();
                    Profilers.Remove(e.MethodName);
                }
            }
            catch { throw; }
        }

        public void Exception(object sender, Aspectus.EventArgs.Exception e)
        {
            try
            {
                if (Profilers.ContainsKey(e.InternalException.TargetSite.Name))
                {
                    Profilers[e.InternalException.TargetSite.Name].Stop();
                    Profilers[e.InternalException.TargetSite.Name].Dispose();
                    Profilers.Remove(e.InternalException.TargetSite.Name);
                }
            }
            catch { throw; }
        }

        private Dictionary<string, Profiler> Profilers { get; set; }
    }


This aspect uses the Profile attribute, defined on a class, to indicate whether or not to profile the functions of a class. Note that it uses my rather poorly made profiler from my utility library. But it works well enough. We could even have an aspect that did this for an individual method, for example caching or validation (maybe you only want to validate when function X is called). The point is, no IL is required and we can accomplish quite a bit. Anyway, that's all there is to it. Any time Create is called, we will be able to get our sub class which is full of aspects waiting to do work for us. Pretty simple actually.

Anyway, I'll be releasing this code with a couple aspects as examples in the near future. In the mean time, take a look, leave feedback, and maybe come up with your own approach to AOP. Maybe you can think of something simpler or more powerful than what I have here. For now, happy coding.
kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 7/20/2010 at 10:03 AM
Tags: , , , , ,
Categories: C#
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed