Designing a .NET Program that Support Plugins
Published: 8/2/2005
Using proper object oriented programming techniques to build an application that supports functionality extension through plugins is a matter of implementing class interfaces that can define a contract between your application and the class libraries (containing these plugins), even without requiring a reference to the plugin libraries when you compile your application.
The architecture of your application will be like this: your distributable is made up of an exe and (1 or more) dll. The dll exposes an interface, which we will call IPlugin. This interface can be as complex or simple as you need it to be, depending on how much information must be passed between your application and each plugin. Your exe will reference this dll, as will all of your plugin class libraries. The "contract" inherent in the interface will serve as the link between your application and any plugins, even though your application doesn't have a reference to these class libraries when it is compiled.
Now on to the code. Here's a plugin interface I used in one of my applications recently. It's very simple, and you will probably have to extend it to support your functionality, whatever that might be. This particular plugin simply exists as a menu option in the main program, and doesn't have any awareness of the functionality of the main app at all:
Public Interface IPlugin ReadOnly Property MenuCaption() As String ReadOnly Property ParentMenuCaption() As String Sub onMenuClicked(ByVal sender As Object, ByVal e As System.EventArgs) Sub SetStatusDelegate(ByVal Value As Status) Delegate Sub Status(ByVal Message As String) Property Owner() As System.Windows.Forms.Form End Interface
Pretty simple, really. If you picture the main applicatiion having previous awareness of this interface, then you can see that any class that implements it can have a presence in the main program's menu, can respond to a user clicking on this menu, can send messages to the main program (via the Status delegate), and can display modal dialogs relative to the main window (using the Owner property). Of course you can extend the functionality of this interface to suit your business logic needs by adding whatever properties or methods you need. Perhaps the "onMenuClicked" method will take a "Document" parameter, so your plugin can act on whatever document the user has open.
So now, each DLL that contains the functionality that you want to plug in to your main application has to expose at least one class that implements this interface. Through this interface, the main program can interfact with your plugin, without requiring any knowledge of the plugin when you compile the main program.
Here's the tricky part - how do you load an instance of this new class without having an explicit reference to it when you compile your main program? Reflection is the answer, and given the path to the dll containing the class implementing your IPlugin interface (you can prompt your user for the path to the plugin, and thereby distribute it separately, or whatever you want), your main program can dynamically load your plugin class.
Here's the code:
AssemblyPath: contains the file path to the class library exposing one or more classes implementing the IPlugin interface PluginInterfaceName: contains the fully qualified name of the interface, for example ";myApp.IPlugin";
Dim clsType As Type
Dim clsInterface As Type
Dim boolAdded As Boolean
Dim clsPlugins As New Collection
Dim clsAssembly As System.Reflection.Assembly
Try
clsAssembly = System.Reflection.Assembly.LoadFrom(AssemblyPath)
Catch
Throw New System.Exception(";An error occured while attempting to access the specified Assembly.";)
End Try
'look for appropriate types...
For Each clsType In clsAssembly.GetTypes
'only look at types we can create...
If clsType.IsPublic = True Then
'ignore abstract classes...
If Not ((clsType.Attributes And System.Reflection.TypeAttributes.Abstract) = System.Reflection.TypeAttributes.Abstract Then
'check for the implementation of the specified interface...
clsInterface = clsType.GetInterface(PluginInterfaceName, True)
If Not (clsInterface Is Nothing) Then
'add the plugin to a collection of plugins, loading it into memory...
clsPlugins.Add(LoadPlugin(AssemblyPath, clsType.FullName)) : boolAdded = True
End If
End If
End If
Next
If Not boolAdded Then Throw New System.Exception(_
String.Format(";The referenced assembly does not contain any plugins matching the {0} interface.";, _
PluginInterfaceName))
Private Function LoadPlugin(ByVal AssemblyPath As String, ByVal ClassName As String) As IPlugin
Dim clsRet As Object
Dim clsAssembly As System.Reflection.Assembly
clsAssembly = System.Reflection.Assembly.LoadFrom(AssemblyPath)
If args Is Nothing Then
clsRet = clsAssembly.CreateInstance(ClassName)
Else
clsRet = clsAssembly.CreateInstance(ClassName, False, Nothing, Nothing, args, Nothing, Nothing)
End If
Return clsRet
End Function
This code should give you the basics for creating an application that supports plugins. As mentioned earlier, extending the interface to support the functionality of your program is a matter of adding functionality to the interface that is shared by your application and plugins.
One caveat of this design is that any references that are shared by your plugin and your main program must be the same version because the .NET framework will search for the reference using the application's path, not the path of the class library. If the application references a dll (for example, myclass.dll; version 1.0.0.1) and your plugin class references the same library but a different version (myclass.dll, version 1.0.0.0), then when loaded into the memory space of the main application the reference in the class library will end up being resolved to the version in the path of the main application, even if the older version is installed in the same path as the class library.
NOTE: This has been turned into a FAQ at VBCity.com.
Written by Scott Waletzko for Skystone Software.
Copyright 2005-2007 by Echosoft Design Studios, LLC, All Rights Reserved.