(Print this page)

Adding plug-ins to your applications
Published date: Monday, August 1, 2005
On: Moer and Éric Moreau's web site

Have you ever think of extending an application already distributed? Have you ever think of distributing some modules or features of your applications only to some users or clients? The plug-ins technique might be the right method to let you do that. Implementing a simple plug-in engine into your own applications is not as hard as it may sound. This column will show you a small example of how to do that. The example will also make the use of an XML file to configure available plug-ins.

But what is a plug-in?

Here is the official answer from Answers.com:
An auxiliary program that works with a major software package to enhance its capability. For example, plug-ins are widely used in image editing programs such as Photoshop to add a filter for some special effect. Plug-ins are added to Web browsers to enable them to support new types of content (audio, video, etc.). The term is widely used for software, but could also be used to refer to a plug-in module for hardware.

Reference

The idea of writing this column came after I saw an example on Planet Source Code web site. I got the permission of the author (Nick Pateman) to use his example as the basis for this column. You can find the original example on Planet Source Code.

I let most of Nick’s code intact so that you can easily re-use his code in case he updates his example.

The 3 layers

Nick created his plug-in engine using 3 layers:

  • An interface that all plug-ins will have to implement. The purpose of this interface is to ensure that each plug-ins will contain the same public interface (list of available properties and methods). If a class header contains the clause to implement this interface and the real code does not appear in this class, the project does not compile.
  • A hosting application that will give a way to the user to enumerate available plug-ins and launch features from them. Thanks to the interface layer, you know exactly what a plug-in is able to do.
  • The last layer contains the plug-ins. Each plug-in implement the interface of the first layer to ensure that required members are there.

Each layer corresponds to a separate project into your solution. Each layer will generate a separate assembly. This means you will have at least 3 assemblies (but you can have more then 3 if you have many plug-ins or if your hosting application already contains many assemblies).

There are many other ways of doing that but I really like the idea of creating an interface to force plug-ins developer to publish a known set of properties and method. This will ease the use of the plug-ins by the hosting application.

Layer 1: The Interface

This layer is the smallest one. It should contain a single class that is the interface that each plug-ins will need to implement. The interface will also be used by the hosting application to avoid late binding (this way your Option Strict clause can still be set to On).

In Nick’s example, this layer is contained into the project called Interface that contains a single class called IPlugin.

This class contains only 5 lines of text:

Public Interface IPlugin

    ReadOnly Property Name() As String

    Sub About()
    Sub DisplayPlugInForm(ByRef pParentForm As Form)

End Interface
You will first notice that the first and the last line contain the word Interface (instead of Class or Module). You will also notice that no code is in there, only members’ declaration. The actual code will be in the plug-ins. All these members are considered Public. Those who are using Nick’s example will notice that I have added a new method called DisplayPlugInForm to the interface.

You will need to modify this layer to add any members you want your plug-ins to have.

Layer 2: The hosting application

I have modified Nick’s hosting application to result in a MDI application.

The first you need to do in the hosting layer is to add a reference to the interface layer (using Project->Add Reference… menu item). You can add a reference using the Projects tab because you probably haven’t compiled the Interface layer yet.

Figure 1: Adding a reference

You will then need to copy 2 classes from example (PlugInEngine and IPlugIn_Collection) into your hosting application. You can copy them as-is without further investigation. These are used as wrappers around your plug-ins since your application has absolutely no idea of which plug-in will be available at runtime. No reference to plug-ins assemblies will be added to your hosting application.

You will need a way to enumerate available plug-ins. See the form called Form1 from the project called host.

The Load button initializes the class giving the name of your configuration file (more on this later) and also calls the LoadPlugins method from the PluginEngine. This method fills a collection of the plug-ins that are found. If plug-ins are found, these are enumerated into a listbox.

Once the collection gets filled, you can call any members defined by the interface. For example, the About button uses this code to show the about box form found into each plug-in:

If (lisPlugins.SelectedIndex <> -1) Then
    pPeeEngine.plugins.item(lisPlugins.SelectedIndex).About()
End If

Do you remember that I have added a new member to the interface? This member is a method that accepts a MDI container as the argument and is used to display the form (that is compiled inside the plug-in assembly) as a child of the hosting application. Isn’t it nice? Here is the code:

If (lisPlugins.SelectedIndex <> -1) Then
     Try
         pPeeEngine.plugins.item(lisPlugins.SelectedIndex).DisplayPlugInForm(Me.MdiParent)
     Catch ex As NotImplementedException
         MessageBox.Show("This feature has not been implemented by this plug-in!")
     End Try
End If

As you can see using forms (or any other members) from a plug-in is not very hard. You simply need a reference to it and the collection is holding the reference for us.

Layer 3: The plug-ins

The last layer consist in one or more assembly, each assembly containing a plug-in. When you create a new project for a plug-in, select the “Class Library” template. Once your project is created, be sure you add a reference to the interface just like you did for the hosting application.

Then you need to create a class that implements this interface. This class contains the code related to the declarations you wrote into the interface. Here is an example of one of this class:

Option Strict On

Imports projectplugin.interface

Public Class cPlugInEMoreau
    Implements IPlugin


#Region "Private declarations"

    Private Const IPlugin_name As String = "cPlugInEMoreau"

#End Region

#Region "Property interface"

    Public ReadOnly Property Name() As String Implements IPlugin.Name
        Get
            Return (IPlugin_name)
        End Get
    End Property

    Public Sub About() Implements IPlugin.About
        Dim f As New fAbout
        f.ShowDialog()
    End Sub

    Public Sub DisplayPlugInForm(ByRef pParentForm As System.Windows.Forms.Form) _
        Implements IPlugin.DisplayPlugInForm
        Dim f As New fPlugInForm
        If Not (pParentForm Is Nothing) Then
            f.MdiParent = pParentForm
        End If
        f.Show()
    End Sub

#End Region

End Class
The interface was containing the declaration of 3 members. Here you have their implementation. The keyword Implements (that you can find 4 times do the magic).

See what the About method and the DisplayPlugInFrom method are doing. Both are displaying a form that can be found into the plug-in assembly. That’s it!

Figure 2: The plug-ins in action

The configuration file

There is one thing I told you that I was to explain later (and I really think it’s about time!). The LoadPlugins method (found into the hosting application) relies on a file that lists the plug-ins, their path, and the class that serves as the entry point. In this example, this file is called hostconfig.xml (and the name is fully configurable since you pass it to the ConfigFile property – see the Load button of the hosting application). The file currently contains these entries:

<?xml version="1.0"?>
<plugins>
	<plugin>
		<path>\plugins\plugin.dll</path>
		<type>plugin.myPlugin</type>
	</plugin>
	<plugin>
		<path>\plugins\PlugInEMoreau.dll</path>
		<type>PlugInEMoreau.cPlugInEMoreau</type>
	</plugin>
</plugins>
You surely found the pattern! You need to add one “plugin” element for each plug-in that will be made available to your application. The beauty of this file is that you can have more then one version of it. If you build your application using this method, you simply do not include the module into the configuration and the user will not be able to use it (even if the assembly is currently on his computer). The “type” element contains the root name space and the class name that implements the interface.

Distribution

Nick decided to create a folder (named plugins) to contain all the plug-ins assemblies. The configuration file also dictates to explore this folder to find assemblies. That means that every time you rebuild your plug-in assembly, you need to copy your .dll file into this folder (something you will surely forget many times!).

Debugging

The bad thing about that is that debugging your plug-in assembly will be harder. I suggest that you create dummy application that will directly reference the plug-in assembly as you normally do and use it without all this plug-in plumbing. Once your assembly is fully debugged, copy it to your plug-ins folder and restart your hosting application.

Conclusion

I really like the idea of plug-ins to add features that may not be distributed to all users/customers. The way Nick encapsulate it is quite easy to use.

I hope you appreciated the topic and see you next month.


(Print this page)