Click here to Skip to main content
Click here to Skip to main content

A Flexible Plugin System

By , 3 Sep 2008
 

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>();

        /// <summary>
        /// This name is used to determine dependencies, should always be in English. 
        /// Should not be confused with the human friendly name in 
        /// <see cref="IPlugin.PluginInfo"/>.
        /// </summary>
        public string PluginName
        {
            get { return _pluginName; }
        }

        /// <summary>
        /// Information about the plugin.
        /// </summary>
        public IPluginInfo PluginInfo
        {
            get { return _pluginInfo; }
        }

        /// <summary>
        /// Other plugins that this one depends on. 
        /// The list should contain <see cref="PluginName"/>s.
        /// </summary>
        /// <value>Is never null</value>
        public IEnumerable<string> Dependencies
        {
            get { return _dependencies; }
        }

        /// <summary>
        /// Start the plugin
        /// </summary>
        /// <param name="application">Application interface exposed 
        /// towards the plugins.</param>
        public void Start(IApplication application)
        {
            IMyApplication myApplication = (IMyApplication) application;
            myApplication.RegisterComponent<IWorldPeace>(new WorldPeaceThroughLove());
            myApplication.AddMenuItem("Click me", OnClick);
        }

        /// <summary>
        /// Invoked when the user clicks on our menu item
        /// </summary>
        /// <param name="name"></param>
        private void OnClick(string name)
        {
            Console.WriteLine("Omg! You clicked me!");
        }

        /// <summary>
        /// Function that must be implemented
        /// </summary>
        /// <param name="name"></param>
        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)
    {
        // here we should add the menu item to a real application
    }

    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()
    {
        // Create a plugin manager and load it
        _pluginManager = new PluginManager<IMyPlugin>(this);

        // plugins are in the same folder as the application and 
        // their file names end with "Plugin.dll"
        _pluginManager.PluginPath = "*Plugin.dll"; 
        _pluginManager.Start();
    
        // Call a method defined in the plugin                       
        _pluginManager["MyPlugin"].SayHelloTo( "everyone!");

        // Access a registered component.
        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

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Member
Freelance developer/architect with a passion for code quality, architecture, refactoring, networking and threading.
 
Solid skills in .NET/C#/MVC3
 
Blog: http://blog.gauffin.org

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSyntax Question about the Source CodememberSanjay Fisk15 Oct '09 - 9:17 
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!
AnswerRe: Syntax Question about the Source Codememberjgauffin15 Oct '09 - 20:17 
http://xunit.codeplex.com
 
It's from Xunit, a testing framework and the replacement of nunit.
GeneralThanksmemberAndré Baltazar5 Sep '09 - 2:34 
Thanks this helped me alot Smile | :)
Generalhelp , newbe in cs (just installed VC++ 2005 pro evaluation version) [modified]memberK.O.8 Sep '08 - 21:15 
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

GeneralRe: help , newbe in cs (just installed VC++ 2005 pro evaluation version)memberjgauffin8 Sep '08 - 21:23 
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[^].
GeneralRe: done this , got exception.memberK.O.8 Sep '08 - 22:10 
------------------------------------------------------------------------------------------
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) Big Grin | :-D
 
only simple code

GeneralRe: done this , got exception.memberjgauffin8 Sep '08 - 22:16 
Where do you get NotImplementedException?
GeneralNot badmemberPIEBALDconsult3 Sep '08 - 10:51 
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.
GeneralSystem.AddInmemberSzymon Pobiega3 Sep '08 - 10:11 
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?
GeneralRe: System.AddInmemberPIEBALDconsult3 Sep '08 - 11:01 
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.
GeneralNot impressedmemberAnton Bredikhin2 Sep '08 - 22:13 
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...
GeneralRe: Not impressedmemberjgauffin2 Sep '08 - 22:26 
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....
GeneralPlugin and host communicationmemberjake782 Sep '08 - 21:41 
Is there a way to get a plugin to communicate with the host?
 
// Jacob
GeneralRe: Plugin and host communicationmemberjgauffin2 Sep '08 - 21:47 
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[^]
GeneralRe: Plugin and host communicationmemberjake782 Sep '08 - 21:50 
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.
GeneralRe: Plugin and host communicationmemberjgauffin2 Sep '08 - 21:55 
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");
  }
}

GeneralRe: Plugin and host communicationmemberjake782 Sep '08 - 21:57 
Thank you, I'll try that.
GeneralLittle Error on Build ...memberJammer1 Sep '08 - 23:38 
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
 

GeneralRe: Little Error on Build ...memberjgauffin1 Sep '08 - 23:49 
It have now been fixed.
GeneralRe: Little Error on Build ...memberJammer2 Sep '08 - 7:27 
Fantastic! Will download and take another look! Thanks v.much chap ...
 

GeneralSharpdevelopmemberRamon Smits1 Sep '08 - 1:07 
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.
GeneralRe: Sharpdevelopmemberjgauffin1 Sep '08 - 1:40 
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.
GeneralRe: SharpdevelopmemberRamon Smits1 Sep '08 - 1:51 
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.
GeneralRe: Sharpdevelopmemberjgauffin1 Sep '08 - 2:01 
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.
GeneralRe: SharpdevelopmemberRamon Smits1 Sep '08 - 1:59 
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.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 3 Sep 2008
Article Copyright 2008 by jgauffin
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid