Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Flexible Plugin System

0.00/5 (No votes)
3 Sep 2008 1  
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>();

        /// <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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here