A Word Before We Start.
As you may know, or not in my spare time when I don't work or study I'm developing some open source components. The first one is something named DocX that's primarily an Addin and code beautifier, that makes the life of a dev easier, however it can't be downloaded from nowhere yet as the language it uses to define code and commenting rules is harder to learn then assembler ;-), this was my first scripting language attempt that's why, so the grammar needs changing.
The next one is available at codeplex and it's working quite well (codename: InternalClasses), it is meant to be a collection of classes that are helpful and accomplish very complex tasks, so they cut the time of developing injection containers, caching, validation, functional programming, and provide extensions to NET modules at least thats the road map. Right now it has only dynamic invocation and code injection and validation.
Lets Get To The Point.
Let's look at internal classes and the code injection feature and what can we do with it. This brings us to some cool possibilities when dealing with plugins. Normally to do a plugin architecture you need a Host class and it's interface and the plugin class that will share some interface from the host class, then the host class loads the plugin assembly and uses the interface that plugin class is implementing. This puts some heavy constraints on the architecture, first the plugin class needs to be coupled around the interfaces of the host, and it has only some degree of control that is limited by the interface.
But with the use of code injection and attributes we can free our selfs from this limitations, and attach our plugin code where we want, this of course can be dangerous but there are also ways to limit that.
So let's show what we want to do on a component diagram:
Note: It's kinda impossible to show it on any diagram as we are dealing with code injection here, so this is a pseudo component diagram, that has some extra elements.
Application host can be extended by some plugin component that holds a collection of attributes, but the injection is happening in the application host component using the InternalClasses component, so we can limit the plugin, so it doesn't do anything that can hurt the application.
Of course this hasn't to be my code injection component there are plenty others but I know mine and know how it works so Ill use mine ;-)
Before We Go To Code.
There are three main techniques when dealing with code injection patterns.
Pre Code Injection.
Runtime Code Injection.
Post Code Injection.
The first one isn't really a code injection kind of thing, it could be implemented as an addin to IDE, as it will happen before building the code, the compiler will do a round trip and add code, it is generally considered safe.
The second one can be spited and we could say that either we create a dynamic proxy type, and wrap the methods adding our code, and that's considered safe as long we get the Il code right. The second one can be done by changing the existing type by manipulating it's bytes in memory, and function pointers. (oh yes it can be done in C#), but that's very dangerous, we are messing with CLR and JIT, and if a new framework version will be out or some patch, everything can crash as the memory might be mapped in a different manner.
The third one is post, so we will rip the compiled assembly apart, change it and save it again, it's generally safe but it can cause a lot of problems with code signing, the late sign assembly might come in handy here.
Now that we have the theory down, we can move forward.
Show Me The Code.
Now let's start to define this via code, first we will need our Host class, and a interface to it so we can only be injected to those methods. Let's start with something simple like a Console Application.
/// <summary> /// Host Interface that defines host functionality. /// </summary> public interface IConsoleHost { /// <summary> /// Writes the specified input. /// </summary> /// <param name="input">The input.</param> void Write(string input); } /// <summary> /// The Console Host that exposes some console methods. /// Implements the <see cref="IConsoleHost"/> /// </summary> public class ConsoleHost : IConsoleHost { #region IConsoleHost Members /// <summary> /// Writes the specified input. /// </summary> /// <param name="input">The input.</param> public void Write(string input) { Console.WriteLine(input); } #endregion }
That's super simple class but it will be sufficient to show the plugin architecture. Next we will define a interface that will annotate that this is a plugin class and it should be checked, I left it empty, but in a more complex scenario it would probably be defined.
/// <summary> /// Notification interface, addnotates that the class /// in a assembly exposes plug in methods. /// </summary> public interface IPlugin { }
Now I know that some say that this is an anti pattern, and I should prefer attributes, but I like interfaces more even for a stupid annotation as they are faster, because attribute checking requires the use of reflection. Now it's time to define our attribute that will plug the method, we could have more attributes that do different operations with the host itself, but I will stick just to one general.
/// <summary> /// The Plug To Attribute. /// </summary> [global::System.AttributeUsage (AttributeTargets.Method, Inherited = false, AllowMultiple = true)] public class PlugToAttribute : Attribute { private Type typeToPlug; private Type interfaceToPlug; private string method; private PlugInOrder order; /// <summary> /// Initializes a new instance of the <see cref="PlugToAttribute"/> class. /// </summary> /// <param name="typeToPlug">The type to plug.</param> /// <param name="interfaceToPlug">The interface to plug.</param> /// <param name="method">The method.</param> public PlugToAttribute(Type typeToPlug, Type interfaceToPlug , PlugInOrder order, string method) { this.typeToPlug = typeToPlug; this.interfaceToPlug = interfaceToPlug; this.order = order; this.method = method; } /// <summary> /// Gets or sets the interface to plug. /// </summary> /// <value>The interface to plug.</value> internal Type InterfaceToPlug { get { return interfaceToPlug; } set { interfaceToPlug = value; } } /// <summary> /// Gets or sets the type to plug. /// </summary> /// <value>The type to plug.</value> internal Type TypeToPlug { get { return typeToPlug; } set { typeToPlug = value; } } /// <summary> /// Gets or sets the method. /// </summary> /// <value>The method.</value> internal string Method { get { return method; } set { method = value; } } /// <summary> /// Gets or sets the order. /// </summary> /// <value>The order.</value> internal PlugInOrder Order { get { return order; } set { order = value; } } }
Now that all elements are in place in the main application, we can define the plugin that will simply add line numbers to the console input that we will write. Note that the ConsolePlugIn is defines in another assembly.
/// <summary> /// The Console Plug In that exposes plugin functionality. /// Implements the <see cref="IPlugin"/>. /// </summary> public class ConsolePlugIn : IPlugin { private int index; /// <summary> /// Displays the console line number. /// </summary> /// <param name="parameters">parameters.</param> /// <param name="meta">meta.</param> [PlugTo(typeof(ConsoleHost), typeof(IConsoleHost), PlugInOrder.before, "Write")] public void LineNum(object[] parameters, object[] meta) { Console.Write(index + ". "); index++; } }
The InternalClasses for code injection requires a specific interface to know what parameters to pass, so every method that is being injected needs to have a object[],object[] parameters. This is because the first array consists of the parameter passed to the original method, and the second array is for metadata, for e.g some data from attributes. Now the only thing that's left is to create the host object, load plugins and inject the code.
public static void Main(string[] args) { Type pluginType = typeof(IPlugin); //put dll load code here, insted of this dummy. Assembly assembly = Assembly. LoadFile(@"C:\Users\Vizzard\Desktop\InternalClasses\PlugInConsole\bin\Debug\PlugInConsole.dll"); IConsoleHost host = null; foreach (Type plugInType in assembly.GetTypes()) { if (pluginType.IsAssignableFrom(plugInType)) { foreach (var plugInMethod in plugInType.GetMethods()) { PlugToAttribute[] plugAttribute = (PlugToAttribute[])plugInMethod.GetCustomAttributes(typeof(PlugToAttribute), false); if (plugAttribute != null && plugAttribute.Length != 0) { PlugToAttribute plugin = plugAttribute[0]; InternalClasses.Dynamic.DynamicType plug = new InternalClasses.Dynamic.DynamicType(); plug.DefineType(plugin.TypeToPlug, plugin.InterfaceToPlug); plug.OpenMethodForCreation(plugin.TypeToPlug.GetMethod(plugin.Method)); plug.AddInjectedMethod(plugInMethod, (InternalClasses.Dynamic.InjectionType)plugin.Order, new object[] { }); plug.CreateMethod(); host = (IConsoleHost)plug.CreateInstance(); } } } } host.Write("This is my console host."); host.Write("and it has been plugedin with additional, line numering code,"); host.Write("that decouples it from a concreate interface via plugin side."); host.Write(string.Empty); Console.ReadKey(); }
What we do here is, load a plugin assembly then check if there are classes that implement the IPlugin, and then simply get the attributes from the methods, and if they are present create a DynamicType, from InternalClasses injecting the code in the methods, what is really going on here? We just create a Proxy class for our original class and wrap the methods that have the attributes, adding the calls to our plugin methods, code side it looks like this:
public class DynamicType : ConsoleHost, IConsoleHost { public void Write(string input) { object[] methodData = new object[1]; methodData[0] = input; object[] metaData = new object[0]; LineNum(methodData, methodData); base.Write((string)methodData[0]); } }
Show Time.
Let's test our application, here are the results:
As you can see it's working, so our plugin architecture is complete, and our plugin class didn't had to use some concrete interface that the Host encapsulates to do some method call, we can plug to any method that's exposed by any interface in the application that's exposed. There are of course places in this article that can be extended and done better like for e.g in the attribute we needed to use the method name as string and if there are more then one methods like this there would be problems, but that's easy to fix, another thing is that we could not use the method name but some descriptor structure so that we don't care about method names and params, but are given some class with enums where we set the place of injection in a call tree of the host.
But like I said (wrote) those things are just enchantments to the architecture, the most important thing here was the plugin architecture based on the runtime code injection.
That is all.
No comments:
Post a Comment