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

A Flexible Plugin System

, 3 Sep 2008 LGPL3
Rate this:
Please Sign up or sign in to vote.
A generic plugin system used to load and manage plugins

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

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

        /// <span class="code-SummaryComment"><summary></span>
        /// Information about the plugin.
        /// <span class="code-SummaryComment"></summary></span>
        public IPluginInfo PluginInfo
        {
            get { return _pluginInfo; }
        }

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

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

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

        /// <span class="code-SummaryComment"><summary></span>
        /// Function that must be implemented
        /// <span class="code-SummaryComment"></summary></span>
        /// <span class="code-SummaryComment"><param name="name"></param></span>
        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)

Share

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralNot bad PinmemberPIEBALDconsult3-Sep-08 11:51 
GeneralSystem.AddIn PinmemberSzymon Pobiega3-Sep-08 11:11 
GeneralRe: System.AddIn PinmemberPIEBALDconsult3-Sep-08 12:01 
GeneralNot impressed PinmemberAnton Bredikhin2-Sep-08 23:13 
GeneralRe: Not impressed Pinmemberjgauffin2-Sep-08 23:26 
GeneralPlugin and host communication Pinmemberjake782-Sep-08 22:41 
GeneralRe: Plugin and host communication Pinmemberjgauffin2-Sep-08 22:47 
GeneralRe: Plugin and host communication Pinmemberjake782-Sep-08 22:50 
GeneralRe: Plugin and host communication Pinmemberjgauffin2-Sep-08 22:55 
GeneralRe: Plugin and host communication Pinmemberjake782-Sep-08 22:57 
GeneralLittle Error on Build ... PinmemberJammer2-Sep-08 0:38 
GeneralRe: Little Error on Build ... Pinmemberjgauffin2-Sep-08 0:49 
GeneralRe: Little Error on Build ... PinmemberJammer2-Sep-08 8:27 
GeneralSharpdevelop PinmemberRamon Smits1-Sep-08 2:07 
GeneralRe: Sharpdevelop Pinmemberjgauffin1-Sep-08 2:40 
GeneralRe: Sharpdevelop PinmemberRamon Smits1-Sep-08 2:51 
GeneralRe: Sharpdevelop Pinmemberjgauffin1-Sep-08 3:01 
GeneralRe: Sharpdevelop PinmemberRamon Smits1-Sep-08 2:59 
QuestionSomeone rated 1? Pinmemberjgauffin31-Aug-08 4:34 
AnswerRe: Someone rated 1? PinmemberUnruled Boy31-Aug-08 20:34 
GeneralYour downloaded example Pinmemberdevnet24730-Aug-08 22:22 
GeneralRe: Your downloaded example Pinmemberjgauffin31-Aug-08 4:28 
GeneralRe: Your downloaded example Pinmemberdevnet24731-Aug-08 5:55 
GeneralVery good (5) [modified] Pinmemberkurre8530-Aug-08 11:23 
GeneralRe: Very good (5) Pinmemberdevnet24730-Aug-08 22:10 
GeneralRe: Very good (5) Pinmemberjgauffin31-Aug-08 4:32 
GeneralRe: Very good (5) Pinmemberkurre8531-Aug-08 5:04 
GeneralRe: Very good (5) PinmemberBillWoodruff2-Sep-08 16:31 
GeneralRe: Very good (5) Pinmemberjgauffin3-Sep-08 2:47 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411022.1 | Last Updated 3 Sep 2008
Article Copyright 2008 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid