PluginFX Article

PluginFX - An extensible plugin framework in C#                    Author:  Willem J Fourie

Source Code for this Article

The following source code may be extacted from this help file:


Introduction

PluginFX is an easy to use, extensible framework to host executable plugin modules.  These modules may be written in any Microsoft .NET language or any COM compliant dynamic link library generator such as Visual C++, Visual Basic, etc.  The framework provides functionality to register/un-register plugins, as well as to manage the loading, execution, and unloading of individual plugins.

 

The framework has been written with the objective of being completely customizable and extensible.  It therefore is relatively 'bare-boned', addressing only the issues of registering and managing plugin assemblies, and allowing an application easy access to a set of registered plugins.  The idea is for interested readers to take the source-code and extend the framework to meet their specific requirements.

The Framework Static Structure

The class structure for the framework is shown in Figure 1 below:

 

 

Figure 1: Framework Static Structure

 

The PluginHost class is implemented in such a way that it can be extended by sub-classing.  The Initialize(), Terminate() and other methods can be overridden in a sub-class to provide application-specific processing.  Internally, PluginHost maintains a Hashtable (System.Collections) as the collection object for multiple plugins.  By implementing the ICollection and IEnumerable interfaces and implementing the IEnumerator interface in the PluginEnumerator class, the PluginHost object can be manipulated as a plugin collection class directly as follows:

 

 PluginHost host = new PluginHost(..);
 ...   
 foreach(object pluginObject in host)
 {
     IPluginAdaptor adaptor = pluginObject as IPluginAdaptor;
     if(adaptor != null)
     {
         ... 
     }
 }
PluginHost references an abstract class named PluginRegisterInfo for the loading and saving of information for plugins that are registered with the PluginHost class.  The application is expected to provide a specific implementation of the PluginRegisterInfo class in order to Load() and Save() the information.  Further, the implementation is expected to provide an implementation of the static method PluginInfo() which returns an array of PluginRegisterInfo objects representing the registered plugins, and the Delete() method to delete the plugin registration information.

 

A default implementation is provided in the form of a Win32 Registry Information object coded in the class PluginWin32RegistryInfo.  This class provide functionality to load and save information from the operating system registry. Note that it is very simple to provide another class to manage the registration information using an XML file, for example, or any mechanism desired, providing the Load(), Save(), Delete() and PluginInfo() methods are implemented.

 

For a plugin to be recognized and managed as a valid plugin by the PluginHost, it is required that it be implemented as a .NET class library assembly (DLL), or a COM DLL or OCX.  The Plugin Assembly has to provide an implementation of the IPluginAdaptor interface in order for it to be successfully Register()'ed, Load()'ed, Unload()'ed and Unregister()'ed by the PluginHost.  These methods are described in the operation sequences in Figures 2, 3, and 4 below.  The implementation of IPluginAdaptor must exist in one (and only one) class that is implemented in the plugin assembly.  The other application interfaces that are implemented in the assembly are not required to be implemented in the same class as that implementing the IPluginAdaptor.  The functionality around this is demonstrated in the test application which is part of the source code for this article, and the reference documentation for this framework.  PluginHost provides the PluginInterface method to easily reference a specific application interface implemented anyhwere in the plugin assembly. The following code excerpt shows how the PluginHost finds and instantiates a specific interface from a loaded assembly using the .NET reflection API:

 

    protected object LoadPluginFromAssembly(Assembly Assembly, string InterfaceTypeName)
    {
        object pluginObject = null;
        try
        {
            // Walk through each type in the assembly
            foreach(Type type in Assembly.GetTypes())
            {
                // Pick up a class
                if(type.IsClass == true)
                {
                    // If it does not implement the IPluginAdaptor
                    // Interface, skip it
                    if(type.GetInterface(InterfaceTypeName) != null)
                    {
                        // Create an instance of the object
                        pluginObject = Activator.CreateInstance(type);
                        break;
                    }
                }
            }
        }
        catch
        {
            // Do nothing
        }
        if(pluginObject == null)
        {
             throw new NotImplementedException("Assembly:<" + Assembly.FullName + "> " +
                 "does not implement " + InterfaceTypeName);
        }
        return pluginObject;
    }
 

The Framework Operation Sequences

The three important operation sequences are shown below.  They are:

 

Figure 2 below shows the Plugin Register sequence:

 

 

Figure 2: Plugin Register Sequence

 

In the Plugin Register Sequence, the PluginHost RegisterPlugin() method is called.  The LoadPluginFromAssembly() method creates an instance of the plugin IPluginAdaptor interface.  Similarly, a new instance of the application sub-classed PluginRegisterInfo class is created, and its attributes populate with the information from the IPluginAdaptor object.  The information is then saved for further use using the PluginRegisterInfo Save() method.  The IPluginAdaptor Register() method is called, and then the LoadPlugin() method is called.  The LoadPlugin() method is described in the Figure 4 below.  Once the LoadPlugin() method is completed, the PluginRegisterInfo object created is returned to the caller.

 

The IPluginAdaptor Register() method is called to allow the plugin to provide its own functionality when the plugin is registered.  This could, for example, be setting its own registry settings, initializing a database etc.

 

Figure 3 below shows the Plugin Unregister or de-registration sequence:

 

 

Figure 3: Plugin Unregister Sequence

 

In the Plugin Unregister sequence, the PluginHost UnregisterPlugin() method is called.  The FindPlugin() method will return the instance of the IPluginAdaptor interface.  The UnloadPlugin() method is called to unload the plugin from the PluginHost.  This method is described in Figure 4 below.  The IPluginAdaptor Unregister() method is called, then PluginRegisterInfo Delete() method is called to delete the plugin registration information from its permannet store, and finally the PluginHost Refresh()'es the registered and loaded plugin references.  When the UnregisterPlugin() method returns, both the PluginRegisterInfo and Plugin objects are effectively deleted.

 

The IPluginAdaptor Unregister() method is called to allow the plugin to provide its own functionality when the plugin is de-registered.  This could, for example, be removing its own registry settings, de-initializing a database etc.

 

Figure 4 below shows the PluginHost Load and Unload sequences:

 

 

 

Figure 4: PluginHost Load/Unload Sequences

 

In the PluginHost Constructor , the Initialize() method is called.  The default implementation calls the Refresh() method.  The PluginHost establishes the application specific PluginRegisterInfo class and calls the PluginRegisterInfo PluginInfo() method to obtain the information for all the registered plugins. For each PluginRegisterInfo object, the LoadPlugin() method is called.  This methods checks for the existence of a loaded plugin with the same Id as PluginRegisterInfo.Id.  If not found, it is loaded from the appropriate assembly by calling LoadPluginFromAssembly() using the PluginRegisterInfo information.  If the appropriate IPluginAdaptor interface can be instantiated, its OnLoad() method is called.

 

The IPluginAdaptor OnLoad() method is called to allow the plugin to provide its own functionality when the plugin is loaded by the framework.

 

In the PluginHost Destructor, the Terminate() method is called.  The default implementation calls the UnloadPlugin() method for each loaded plugin.  In this method, The plugin reference is removed from the hashtable and the IPluginAdaptor Unload() method is called.

 

The IPluginAdaptor OnUnload() method is called to allow the plugin to provide its own functionality when the plugin is unloaded by the framework.

Conclusion

The basic architecture of a plugin framework has been demonstrated, both in theory and in a code implementation.  Certainly, there is room for improvement in the implementation, but the objective is to provide a solid architecture and perhaps a design-pattern for such frameworks.