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

A more extensible way to build plugin system

0.00/5 (No votes)
29 Aug 2010 1  
This post explains how plugin architecture can be improved to allow better extensibility and provide backward compatibility after updates.

A plugin, for those who don’t know, is a component that allows to extend an application without modifying its source code. The application loads plugins at runtime. In .NET, it’s pretty easy to accomplish using Reflection capabilities. The easy way is to expose interfaces in your application assemblies, that the plugin can implement and use, like:

C#
public interface IPluginHost
{
  void DoSomethingOnHost();
}
 
public interface IPlugin
{
  void Initialize(IPluginHost pluginHost);
  int DoSomething();
}

IPlugin interface represents our plugin – all plugins implement this. IPluginHost interface represents the host application. Plugins must often be able to interact with the host application in order to influence how its working.

From within the application, you can load the plugin assemblies and instantiate the plugins using code like the following:

C#
using System.Reflection;
...
IList<IPlugin> pluginList = new List<IPlugin>();
foreach(string fileName in Directory.GetFiles("Plugins", "*.dll"))
{
  Assembly assembly = Assembly.LoadFile(fileName);
  foreach (Type pluginType in assembly.GetTypes())
  {
    if (!pluginType.IsPublic || pluginType.IsAbstract || pluginType.IsInterface)
      continue;
 
    Type concreteType = pluginType.GetInterface(typeof(IPlugin).FullName, true);
 
    if (concreteType != null)
    {
      IPlugin plugin = (IPlugin)Activator.CreateInstance(concreteType);
      plugin.Initialize(pluginHostInstance);
 
      pluginList.Add(plugin);
 
      break;
    }
}
}

The code is simplified, but you can see the point, I hope. We start by loading every .dll file as an Assembly in our application. Then, we iterate through all the public types in that assembly in search for a type that implements IPlugin interface. Once we found it, we load it using Activator.CreateInstance method, assuming that the type has a parameterless constructor. Then we initialize the plugin, giving it an instance of an IPluginHost interface implementation.

Now this approach works fine and I’ve used it several times. However, you usually get into some problems, when you decide to add new functionality. IPlugin interface represents what we need to know about the plugin. IPluginHost interface represents what host application has to offer to the plugin. Those requirements might change quite often, so you will, most likely, make changes to those interfaces. Suppose that we want to allow our plugins to show a message box through its host. That’s easy, we just add a corresponding method to the IPluginHost interface:

C#
public interface IPluginHost
{
  void DoSomethingOnHost();
  void ShowMessageBox(string text);
}

That’s where it all breaks.

Once we release our application and plugins have started to be developed by other people, we should not make changes to the core interfaces, that the plugins depend upon. The thing is, that when you change some interface on the host, the plugin cannot be loaded, because of interface mismatch. So we no longer have backwards-compatibility. How do we extend the plugin subsystem then?

The way I implement plugins now is a bit different. The goal is to keep the core interfaces unchanged. These must be very stable. How do you allow plugins to use new features of your application then?

That’s quite easy. Instead of having a single interface, like IPluginHost – that is almost sure to become a “dependency magnet” or, in other words, a God object, as we add more features to the host application – I utilize the Interface Segregation Principle. It says: “Clients should not be forced to depend on interfaces that they do not use”. So instead of one, big bloated interface we have many small ones. Whenever we decide to add some new functionality to our host application, we add a corresponding interface that plugins can use. In the case of the message box requirement, that I’ve shown above, we declare this:

C#
public interface IMessageBoxHost
{
  void ShowMessageBox(string message);
}

Now, we have to somehow pass the interface to the plugin. Since we can’t modify IPlugin’s declaration (we can’t just overload Initialize method, since that would break backwards compatibility too), we need another way to do this. What I chose to use is constructor injection. We can detect what arguments the constructor of IPlugin implementation have and pass what it needs. For example, if we have a plugin like:

C#
public class Plugin: IPlugin
{
  public Plugin(IMessageBoxHost messageBoxHost)
  {
    // code here
  }
 
  // method implementations here
}

... we pass an instance of IMessageBoxHost implementation when instantiating the plugin:

C#
(IPlugin)Activator.CreateInstance(concreteType, new object[] { messageBoxHostInstance })

That way, we only get what we need. We can also use any of the Dependency Injection frameworks to instantiate the plugin and fill dependencies for us.

Of course, if you spot some bug in the interface or have to change it for some other reason, the problem remains the same – changing interface will break plugins. But it’s also easier to solve – we can add a new, fixed, interface, say IMessageBoxHostEx, and mark the old one as obsolete, thus maintaining backwards compatibility and warning users to let go of the previous one.

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