Introduction
As applications grow, they tend to be harder and harder to maintain, despite the fact that most applications are more or less well designed from the start. This is usually because it's very easy to increase coupling when you need to add new features to your application. Plugins are a way to solve this by adding new functionality in small testable DLLs.
This example does not contain a lot of text describing everything, instead I'll just show you how to use the plugin system. If you have any thoughts, comment the article or visit the discussion board at Codeplex.
The Plugin System
The plugin system consists of three parts.
The Application
The application creates an instance of the PluginManager which is used to find and load plugins. The plugins are found by specifying a search string which contains directory information and wild cards for the files. You can specify the security settings (Evidence) that each assembly should be loaded with, which is perfect if you have different access levels for your plugins.
Application Interfaces
The second assembly contains the interfaces that the application exposes towards the plugins. This assembly exists since you may not want to send your entire application to third party plugin developers.
Plugins
And finally we got the actual plugin assemblies. Plugins can mark themselves dependent on other plugins. For instance, say that you have a newsletter plugin which generates and sends newsletters. The newsletter plugin needs an emailer which is specified in another plugin. The newsletter plugin can therefore mark itself as dependent on the emailer plugin, as a result of which the emailer will always be loaded before the newsletter plugin.
The Example
In this article, I'll demonstrate how to create a simple plugin.
First of all, we need to specify what the plugins should be able to do, and define which parts of the application are accessible from the plugins. All of this is added into a class library that will be sent to plugin developers.
Applications Shared Assembly
In this example, our application wants to let the plugins be able to register components and add menu items to the main application menu. By letting plugins register components, we got a quite flexible solution since plugins can use components registered by other plugins.
public interface IMyApplication : IApplication
{
void AddMenuItem(string name, MenuItemClickedHandler clickHandler);
void RegisterComponent<T>(T component) where T : class;
T GetComponent<T>() where T : class;
}
public delegate void MenuItemClickedHandler(string name);
And the interface that plugins should implement:
public interface IMyPlugin : IPlugin
{
void SayHelloTo(string name);
}
We don't have high expectations from the plugins, they are just required to say hello to anyone we tell them.
Plugin Interface
The plugin has a component that it will register, thus adding functionality to the application that can be used by the application itself or other plugins.
public interface IWorldPeace
{
void Initiate();
}
The Plugin
The plugin contains two parts: First the class that implements the plugin interface, and then the class that implements IWordPeace that the plugin introduced.
public class MyPlugin : IMyPlugin
{
private const string _pluginName = "MyPlugin";
private readonly IPluginInfo _pluginInfo = new MyPluginInfo();
private readonly IEnumerable<string> _dependencies = new List<string>();
public string PluginName
{
get { return _pluginName; }
}
public IPluginInfo PluginInfo
{
get { return _pluginInfo; }
}
public IEnumerable<string> Dependencies
{
get { return _dependencies; }
}
public void Start(IApplication application)
{
IMyApplication myApplication = (IMyApplication) application;
myApplication.RegisterComponent<IWorldPeace>(new WorldPeaceThroughLove());
myApplication.AddMenuItem("Click me", OnClick);
}
private void OnClick(string name)
{
Console.WriteLine("Omg! You clicked me!");
}
public void SayHelloTo(string name)
{
Console.WriteLine("Hello " + name);
}
}
And finally the world peace implementation:
public class WorldPeaceThroughLove : IWorldPeace
{
public void Initiate()
{
Console.WriteLine("Love is all around!");
}
}
The Application
We use a console application in this example, but it could be a Winforms or a Web application.
public class Program : IApplication
{
private PluginManager<imyplugin> _pluginManager;
private readonly IDictionary<type,> _components = new Dictionary<type,>();
[STAThread]
public static void Main()
{
new Program().Start();
}
public void AddMenuItem(string name, MenuItemClickedHandler clickHandler)
{
}
public void RegisterComponent>T<(T component) where T : class
{
_components.Add(typeof(T), component);
}
public T GetComponent>T<() where T : class
{
return (T)_components[typeof(T)];
}
private void Start()
{
_pluginManager = new PluginManager<IMyPlugin>(this);
_pluginManager.PluginPath = "*Plugin.dll";
_pluginManager.Start();
_pluginManager["MyPlugin"].SayHelloTo( "everyone!");
IWorldPeace worldPeace = GetComponent<IWorldPeace><iworldpeace>();
worldPeace.Initiate();
}
}
Update: Unloading Plugins
I've got some questions about how to unload plugins. I have no support for this in the plugin system and I'll try to explain why.
First of all, there is no way to unload assemblies (DLLs) from an appdomain (application). Period.
Solution: Appdomains and Remoting
Although there's a way around it: using a secondary appdomain for all plugins, this means that the plugins are in something that can be considered as an external application (each appdomain has isolated memory and an unhandled exception in the secondary appdomain will not affect the primary appdomain) which means that you need to use remoting to be able to communicate between the plugins and the main application.
Having all plugins in the secondary appdomain means that you can't unload a single plugin, you have to unload all of them. But hey, you can use one appdomain for each plugin, right? Well sure; if you can live with the performance hit.
Another Problem
Let's say that you have a plugin which provides an addressbook with contacts to the rest of the application. Do you really want to unload it? Let's say that the application (or any of the other plugins) contain references to some of your contacts, what happens to them if you unload that plugin?
Unloading stuff is much more complex than you might think.
Final Words
That's it. Let me know if there's something that you did not understand.
Feel free to check my other projects: C# Webserver and TinyDAL.
History
- 30th August, 2008: Initial post
|
|
 |
 | Syntax Question about the Source Code Sanjay Fisk | 10:17 15 Oct '09 |
|
 |
I'm just starting to read up on plug-in based systems and I found this article to be a very good place to start. Thanks for the work you put into this!
I have a question regarding part of your syntax used in the MonthSpan struct from your source code:
[Fact] private static void TestOneMonth() { DateTime from = new DateTime(2008, 1, 1); DateTime to = new DateTime(2008, 1, 31);
MonthSpan span = Create(from, to); Assert.Equal(1, span.Months); }
What is that "[Fact]" thing that you have going on there? As you see, I don't even know what it's called to try searching for it on google.
Thanks!
|
|
|
|
 |
|
 |
http://xunit.codeplex.com
It's from Xunit, a testing framework and the replacement of nunit.
|
|
|
|
 |
 | Thanks André Baltazar | 3:34 5 Sep '09 |
|
 |
Thanks this helped me alot
|
|
|
|
 |
 | help , newbe in cs (just installed VC++ 2005 pro evaluation version) [modified] K.O. | 22:15 8 Sep '08 |
|
 |
I got the folowing message with the Red Circle and White X dialog box (title: "Microsoft Visual studio") ------------------------------------------------------------------ "A Project with an output type of class Library cannot be started directly.
In order to debug this project, add an executable project to this solution which references the library project. Set the Executable project as the startup project." ------------------------------------------------------------------
my "starup project" is your "Fadd" project.
uaually I work with VC6.0 C/C++ programs and win 2000 SDK. I will be gratefull if you will port this work to a MSVC6.0++ demo (in a one button launch a debug == "F5").
TX K.O.
p.s. My goal is to create a minimal Mozila FireFox PlugIn, and to create a minimal Worper to just download it (the DLL) from a server (like done with ActiveX OCX). The bare minimal neccesarry c++ code to display "hello world" Text on the FireFox DviceContext (or canvas I don't really care about it's name).
by the way did anybody used X-Plain SDK???? I think there is a sample for simple plugin.
only simple code
modified on Tuesday, September 9, 2008 4:04 AM
|
|
|
|
 |
|
 |
You can't use this framework to create a mozilla firefox plugin. You have to use their framework to be able to do that.
Use fadd if you want to add plugins in YOUR application..
To launch the demo, rightclick on "ExampleApplication" and select "Set as startup project" as shown in this screenshot[^].
|
|
|
|
 |
|
 |
------------------------------------------------------------------------------------------ An unhandled exception of type 'System.NotImplementedException' occurred in Fadd.dll
Additional information: The method or operation is not implemented. ------------------------------------------------------------------------------------------
can you advise please?
"Or may be Invoke a solution..." (joke)
only simple code
|
|
|
|
 |
|
 |
Where do you get NotImplementedException?
|
|
|
|
 |
 | Not bad PIEBALDconsult | 11:51 3 Sep '08 |
|
 |
So far I've only written one application that uses plug-ins.
Some additional functionality I have in my plug-in management is the ability to allow the user to disable plug-ins he doesn't want (other users of the system may want them so I can't just delete them), and also to detect new versions of plug-ins.
These settings are saved in an XML file.
... <Plugin Genus="RollDice" Species="RollDice, Version=1.1.0.349" Enabled="True" />
...
When the application starts up and finds plug-ins, it checks the configuration; if the plug-in isn't listed or the version has changed, the user is prompted for what to do.
|
|
|
|
 |
 | System.AddIn Szymon Pobiega | 11:11 3 Sep '08 |
|
 |
Why one should write a plugin system by himself when Microsoft has done a good job including it's plugin system (System.AddIn namespace) in the .NET framework?
|
|
|
|
 |
|
 |
Because that's a .net 3.5 feature and many people have already written their own implementations.
I also don't think it applies to plug-ins, it certainly doesn't solve the whole problem anyway.
|
|
|
|
 |
 | Not impressed Anton Bredikhin | 23:13 2 Sep '08 |
|
 |
I was not the person to rate your article as 1, but it won't receive a 5 from me either.
First of all, if this article is about plugin system, why should readers bother themselves browsing all those dozens of files in the solution you have at codeplex? What all those tests, validation and internalization have to do with a plugin system? How the Command pattern corresponds with it?
About the plugin system itself: 1. How would a plugin release itself in a specific way? Currently I only see a way to initialize it. 2. If IPlugin contains a method to get IPluginInfo, why should it also contain Name property? 4. Why not to use an XML config file for plugins? 3. Other issues...
|
|
|
|
 |
|
 |
Thank you for your comment.
First of all. This article is about the plugin system, yes. But fadd is a framework which contains more, and I have never stated anything else. How can that be a problem? Theres a namespace in fadd which says "Plugins", no need to go through everything else if your not interested..
Your issues:
1. There is no way in .Net to unload an assembly except by using remoting (since it involves secondary appdomains), which means that you either have to create a remoting "bridge" or to fake unloading (the plugins will still be in memory but will not do anything).
2. The name on the plugins are for dependencies, while the name in plugininfo can be localized (since it's displayed to the user).
4. Why is XML config files needed?
3. What other issues....
|
|
|
|
 |
 | Plugin and host communication jake78 | 22:41 2 Sep '08 |
|
 |
Is there a way to get a plugin to communicate with the host?
// Jacob
|
|
|
|
 |
|
 |
What do you mean host?
You mean in a client/server enviroment? Well. I don't think that it's up to the plugin framework to be responsible for that.
If you fancy a command pattern implementation theres one in fadd which also can send commands from a client to the server and vice versa (transparently). You can read more about it here: http://www.codeplex.com/fadd/Wiki/View.aspx?title=Commands&referringTitle=Home[^]
|
|
|
|
 |
|
 |
The main program ie host, loads the plugin (dll) and I want to send information from the plugin to the main program and execute subs in the main program.
|
|
|
|
 |
|
 |
The plugin takes an IApplication parameter in the Start method. Create your own interface with everything in the application (main program) that you want to provide to the plugins.
Or you can use the one in this article to provide components that the plugins can use.
For instance:
class myplugin : IPlugin { public void Start(IApplication application) { MyApplication app = (MyApplication)application; app.GetComponent<IToolbarFactory>().CreatePanel("myPanel", "Something that will appear in the main app toolbar"); } }
Or if you provide it directly in your IApplication implementation:
class myplugin : IPlugin { public void Start(IApplication application) { MyApplication app = (MyApplication)application; app.CreatePanel("myPanel", "Something that will appear in the main app toolbar"); } }
|
|
|
|
 |
|
 |
Thank you, I'll try that.
|
|
|
|
 |
 | Little Error on Build ... Jammer | 0:38 2 Sep '08 |
|
 |
I get:
The command "copy D:\CODE SAMPLES\Plugins\trunk\Examples\Plugins\ExamplePlugin\bin\Debug\ExamplePlugin.dll D:\CODE SAMPLES\Plugins\trunk\Examples\Plugins\ExampleApplication\bin\Debug\" exited with code 1. C:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets 3397 13 ExamplePlugin
|
|
|
|
 |
|
|
 |
|
 |
Fantastic! Will download and take another look! Thanks v.much chap ...
|
|
|
|
 |
 | Sharpdevelop Ramon Smits | 2:07 1 Sep '08 |
|
 |
Have you taken a look at other frameworks like for example the addon/plugin system of sharpdevelop?
http://www.icsharpcode.net/OpenSource/SD/Default.aspx
Your code is logical and all people that create a plugin framework start with a similar design. But this design has lots of design issues like: * Plugin feature detection * Plugin management * Memory allocation * Versioning * Validation/signing of plugins
Not that the sharpdeveloper code base fixes all this but it does most things.
|
|
|
|
 |
|
 |
Thank you for your comments.
* Plugin feature detection
Can you elaborate on what you mean?
* Plugin management
Can you elaborate on what you mean?
* Memory allocation?
Can you elaborate on what you mean?
* Versioning
this is a complex subject and isn't something that is easy to solve. Especially not since plugins can be dependent of each other (thus plugin B would require version 1.0 of plugin A while plugin C would require version 1.1 of plugin A).
* Validation/signing
My system DO handle validation/signing. The plugin assembly's public key is available and can be used to determine at which level plugin should be loaded. I could also change the handling to not load a plugin if no Evidence is returned by the event.
|
|
|
|
 |
|
 |
Feature detection: Loading an assembly and searching for available plugins
Plugin management: Enabling/disabling plugins easily from a UI. For example warning users that certain plugins are not stable due to crash reports.
Memory allocation: Unloading of assemblies when they are not used anymore. AFAIK only possible when loading them in a seperate appdomain.
Validation/signing: I mean only allowing 'whitelisted' plugins or similar functionality. The objective here is to make sure the application is and stays stable.
Also something I forgot.. Plugin configuration. The plugin can be loaded in multiple environments where the plugin can be initialized differently. The framework should provide the plugin the possibility to store "current" settings.
|
|
|
|
 |
|
 |
Feature detection: It does that. (Take a look at the PluginFinder class in the project).
Plugin management: I use the plugins at serverside, so it's nothing I've needed so far.
Memory allocation: Since I use the system at server side, i do not need the ability to unload plugins. If you read another comment you'll see that I've already commented about remoting/separate appdomain for plugins.
Validation: Yep. That's what I said. You can build a whitelist with the public key tokens of the assemblies that you allow.
I coded this plugin system 5 days ago, so it's still quite young. More features will come, though only when I need them unless someone else joins the project.
|
|
|
|
 |
|
 |
ow... and forgot yet another one. The scan output for possible plugins should be cached/stored so that you do not have to scan the whole path again.
Like for example most music sequencers work with VST plugins. Most build a list and store it for reference later on and can be manually updated it users force it.
|
|
|
|
 |
|
|
Last Updated 3 Sep 2008 |
Advertise |
Privacy |
Terms of Use |
Copyright ©
CodeProject, 1999-2010