Author | Message | Time |
---|---|---|
Quarantine | Lately I've been getting into programming in C# and I wanted to write a multipurpose application extendable by plugins which I would use as a "core" for anything I would want to develop in the future. My thoughts on it now are looking into how DLLs differ in .NET if they do at all then finding a way to export an interface from this DLL to have a set of common functions for the plugin so that it may identify itself. After that allow the plugin to register initiation and shutdown functions (or maybe just shutdown because if it's registering functions it must already be initiated!) Each plugin would have the ability to manipulate the host form in someway? Or possibly just be able to be listed in a list of plugins then it can draw whatever windows it would need from there. I also was thinking about ways for plugins to communicate with each other to further allow flexibility but I wouldn't know where to start in the implementation. I know people like Myndfyre have done extensive work on this and all I am looking for is the general theory behind how it would work and comments on my thoughts above. Any feedback is greatly appreciated. Thanks | August 12, 2005, 1:29 PM |
TehUser | You'll probably want to use the System.Reflection namespace, which will use the metadata of a .NET application to allow you to enumerate objects and types and create them within your host application. There are also plenty of examples out there. I highly recommend taking a look at The Razor Framework and even if you don't want to create something as complex as that, it's still good to learn from. | August 12, 2005, 2:42 PM |
Myndfyr | As TehUser said, the primary means of implementing extensibility is through the System.Reflection namespace. You'll use types there such as Assembly as well as the System.Type class. The main thing you'll want to do is establish a contract with plugins. For example, if you create a plugin assembly, there are a few things that you can do to make your loading of that plugin DLL more efficient. Each plugin type inside your assembly will be a class, and to help your class behave in ways that your main application expects, each class can inherit from a base plugin class or interface that you define in your main project. One thing that you could do is implement a custom attribute that is applied to the assembly. Because .NET allows you to define a type constant at compile time (via typeof() in C#), you can get access to a type immediately. Consider this attribute class (which you define in your main project): [code] using System; [AttributeUsage(AttributeTargets.Assembly)] public class PluginContainerAttribute : Attribute { private Type m_type; public PluginContainerAttribute(Type pluginType) { m_type = pluginType; } public Type PluginType { get { return m_type; } } } [/code] Then, in your plugin DLL, you have a reference to the main project (which has to be a DLL), you add this attribute somewhere outside of any namespace declarations (usually AssemblyInfo.cs): [code] [assembly: PluginContainer(typeof(SomePlugin))] [/code] You can put one of those for each plugin in your assembly. Then, you can load each one through reflection: [code] Assembly aPluginDll = Assembly.FromFile("myPlugin.dll"); PluginContainerAttribute[] pluginAtts = aPluginDll.GetCustomAttributes(typeof(PluginContainerAttribute), false) as PluginContainerAttribute[]; Type[] pluginTypes = new Type[pluginAtts]; int numPlugins = pluginAtts.Length; for (int i = 0; i < numPlugins; i++) { pluginTypes[i] = pluginAtts[i].PluginType; ] [/code] Now, you can use Activator.CreateInstance(Type) to create instances of your plugin. This isn't necessary if you're using static methods on your plugin, but I highly recommend it, even if you're only going to make one instance of your plugin. This is much more efficient (base classes and interfaces only operate on nonstatic methods) for the reasons I'm about to give. The only way to get access to static methods is through Type.GetMethod or similar methods. These methods are horribly slow compared to the alternative. My preferred method is to use the .NET IL instructions call or callvirt, which can be done through a base class. Jinx will do this for plugins. For example, ConnectionBase is a base class that has a set of virtual methods, and a Battle.net connection plugin (for example) will inherit from this. This is a type of early binding, which is why it is much more efficient than the late binding of Type.GetMethod. The other, nearly-as-efficient method of doing this is to use interfaces to define the base class that the main system will call. Here are examples of equivalent things: [code] public abstract class PluginBase { public abstract void DoSomething(); } public class PluginBase { public virtual void DoSomething() {} } public interface IPlugin { void DoSomething(); } [/code] Either way you do this (well, given the typename difference), you can get an early-bound reference to your class by using Activator.CreateInstance. (In the plugin project): [code] public class MyPlugin : PluginBase { public override void DoSomething() { Console.WriteLine("Doing something from a plugin."); } } [/code] (In the main project, using things that we did earlier, and if necessary, correct for type naming): [code] PluginBase[] plugins = new PluginBase[pluginTypes.Length]; for (int i = 0; i < numPlugins; i++) { plugins[i] = Activator.CreateInstance(pluginTypes[i]); } for (int i = 0; i < numPlugins; i++) { plugins[i].DoSomething(); } [/code] For more information about efficiency in Reflection, I would refer you to this July 2005 article in MSDN Magazine. | August 12, 2005, 8:55 PM |
Quarantine | I'm still relatively new to C# but I was playing around with Interfaces and looked into both abstract classes and virtual methods within regular classes. I also looked into reflection for getting the methods of a class and thats looks promising, I still need to learn more about assemblies, attributes, and .NET DLL's but it's looking promising. Thanks TehUser and Myndfyre for your help and useful links :). | August 13, 2005, 2:41 AM |