Click here to Skip to main content
15,867,756 members
Articles / Programming Languages / C#

A more extensible way to build plugin system

Rate me:
Please Sign up or sign in to vote.
4.57/5 (7 votes)
29 Aug 2010CPOL3 min read 20.7K   21   14
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Lithuania Lithuania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionEven better way to define a plug-in system Pin
JeremyWilkins29-Sep-14 12:36
JeremyWilkins29-Sep-14 12:36 
GeneralRe: Even better way to define a plug-in system Pin
Gediminas Geigalas30-Sep-14 23:14
Gediminas Geigalas30-Sep-14 23:14 
GeneralRe: Even better way to define a plug-in system Pin
JeremyWilkins8-Oct-14 10:05
JeremyWilkins8-Oct-14 10:05 
Questiondetect the arguments of the constructor? Pin
tttdolph10-Aug-11 12:15
tttdolph10-Aug-11 12:15 
AnswerRe: detect the arguments of the constructor? [modified] Pin
Gediminas Geigalas11-Aug-11 2:51
Gediminas Geigalas11-Aug-11 2:51 
GeneralRe: detect the arguments of the constructor? Pin
tttdolph11-Aug-11 9:38
tttdolph11-Aug-11 9:38 
GeneralMy vote of 5 Pin
Hamish Ahern7-Jul-11 1:16
Hamish Ahern7-Jul-11 1:16 
GeneralGreat Article Pin
Diamonddrake1-Sep-10 5:49
Diamonddrake1-Sep-10 5:49 
You make a lot of good points here. Its not incredibly often I find myself writing software that needs a plugin system but next time I do I know what mistakes to look out for. Thanks.
GeneralMy vote of 5 Pin
Brian Pendleton31-Aug-10 1:56
Brian Pendleton31-Aug-10 1:56 
GeneralMy vote of 5 Pin
CiskeBusch30-Aug-10 19:57
CiskeBusch30-Aug-10 19:57 
General.net4 have native plugin subsystem Pin
vdasus30-Aug-10 13:43
vdasus30-Aug-10 13:43 
GeneralRe: .net4 have native plugin subsystem Pin
Gediminas Geigalas30-Aug-10 19:26
Gediminas Geigalas30-Aug-10 19:26 
GeneralRe: .net4 have native plugin subsystem Pin
vdasus30-Aug-10 20:44
vdasus30-Aug-10 20:44 
GeneralRe: .net4 have native plugin subsystem Pin
JeremyWilkins29-Sep-14 12:48
JeremyWilkins29-Sep-14 12:48 

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

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