Click here to Skip to main content
Click here to Skip to main content

Provide plug-in support in your own applications

, 4 May 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
A tutorial showing how to add plug-in support to your own applications.

Introduction

This tutorial will show how to provide plug-in support in your own applications using plain .NET technologies like interfaces and Reflection.

Background

I think you have already used an application providing plug-in support. The variety of such applications is pretty large - e.g., graphic programs (like GIMP, Adobe Photoshop), text editors (like Notepad++), etc. How could you achieve to provide such support, too - enabling other developers to extend the possibilities of your own applications with their own code?

The solution

To provide Plug-In support, we use basic technologies like interfaces and Reflection. At the end of this tutorial there will be a very brief sample application, to list and call the available interfaces.

Setting up the project

The first thing we have to do is to create a new project. I chose "Console Application" as type and set its name to "DBB.PluginsTest". Leave this project as is so far.

Now we need to create our interface that enables us to gain access to plug-ins and call the plug-in's methods. To achieve this, we set up another project in our solution, which is of type "Class Library", and in my case called "DBB.Plugins.Ext".

The third and last step, setting up the project, is creating our plug-in project. So we add a new project to our solution. Its type is also "Class Library" and I called it "TestPlugin1". For our testing purposes, we change the output path for this project to the output path of our "DBB.PluginsTest" folder - that assures our plug-in resides in our working directory and we do not have to copy it around for any of our tests. To change the properties, right click the "TestPlugin1" project, choose "Properties", and set the output path in the "Build" tab to the appropriate one.

Defining our plug-in interface

Because we do not know what exactly the plug-in does and its methods, properties, etc., we need to define a standard interface which is implemented by every plug-in that shall be used with our application. For our purposes, we create a very basic interface which just holds information about the plug-in's name, its version, and a method to run.

Create a new folder in the "DBB.Plugins.Ext" project called "Interfaces". In this folder, we create a new interface called IPlugin.

namespace DBB.Plugins.Interfaces
{
    public interface IPlugin
    {
        /// <summary>
        /// Gets or sets the plugin's name.
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Gets or sets the plugin's version.
        /// </summary>
        string Version { get; }

        /// <summary>
        /// Runs the plugin's main method.
        /// </summary>
        /// <returns>Some object.</returns>
        object Run();
    }
}

Defining our dummy plug-in

After defining our default plug-in interface, we are going to define our dummy plug-in. To do so we have to add a reference to "DBB.Plugin.Ext" - the library that holds our plug-in interface. After adding this reference, the interface IPlugin is available in the plug-in we are going to implement.

Let's add a new class called TestPlugin11 to the "TestPlugin1" project. This class implements the IPlugin interface and looks as follows:

using DBB.Plugins.Interfaces;

namespace TestPlugin1
{
    public class TestPlugin11 : IPlugin
    {
        /// <summary>
        /// Does something.
        /// </summary>
        /// <returns>Some string.</returns>
        private string DoSomething()
        {
            return string.Format("{0} - Version: {1} -> {2}", Name, Version, "DoSomething()");
        }

        #region IPlugin members.

        /// <summary>
        /// Gets the plugin's name.
        /// </summary>
        public string Name
        {
            get { return "TestPlugin1.1"; }
        }

        /// <summary>
        /// Gets the plugin's version.
        /// </summary>
        public string Version
        {
            get { return "1.0.0.0"; }
        }

        /// <summary>
        /// Runs the plugin's main method.
        /// </summary>
        /// <returns>Some object.</returns>
        public object Run()
        {
            return DoSomething();
        }

        #endregion

    }
}

As you can see, IPlugin has been implemented and the DoSomething() method returns some information about the plug-in. Of course it is also possible to implement a much more complex function.

Fill the test project with life

At this point, our interface is defined and our dummy plug-in is implemented. What we still need is some possibility to find available plug-ins and call their functionality (the Run() method).

First, let's add a new folder to the "DBB.PluginsTest" project; called "PluginsHandling" and a new class PluginUtils within this new folder. Because we want to access our IPlugin interface from our test project, we have to add a reference to the "DBB.Plugins.Ext" project in the "DBB.PluginsTest" project.

The whole plug-in handling is done within just two methods:

  • Retrieving .dll files, which might contain plug-in functionality.
  • Retrieving valid plug-ins from found in .dll files.

First we implement a method that retrieves all ".dll" files of a specified path.

/// <summary>
/// Gets a list of Dll files within a specified path.
/// </summary>
/// <param name="path">Path to retrieve Dll files in.</param>
/// <returns>List of strings with file names.</returns>
public static List<string> GetDllList(string path)
{
    return new List<String>(Directory.GetFiles(path, "*.dll"));
}

After that the interesting part needs to be implemented: Reading the .dll files and checking for valid plug-in functionality.

This check is done via Reflection technologies and looks as follows:

/// <summary>
/// Gets a list of IPlugin objects.
/// </summary>
/// <param name="dllFiles">List of Dll files to check for plugin capabilities.</param>
/// <returns>List of IPlugin objects.</returns>
public static List<IPlugin> GetPlugins(List<string> dllFiles)
{
    List<IPlugin> retVal = new List<IPlugin>();

    foreach (string dll in dllFiles)
    {
        try
        {
            // Load dll.
            Assembly assembly = Assembly.LoadFile(dll);
            // Get class names.
            Type[] types = assembly.GetTypes();

            // Add plugin classes to plugin list.
            foreach (Type cls in types)
            {
                if (cls.IsPublic)
                {
                    // Get classe's implemented interfaces.
                    Type[] interfaces = cls.GetInterfaces();
                    foreach (Type iface in interfaces)
                    {
                        // Is current interface IPlugin?
                        if (cls.GetInterface(iface.FullName) == typeof(IPlugin))
                            retVal.Add(assembly.CreateInstance(cls.FullName) as IPlugin);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    return retVal;
}

What are we doing here?

First we try to load each .dll file (assembly) the provided dllFiles list contains. Then we try to retrieve the class names within the single assembly and check whether the class is public. If so, we try to retrieve the interfaces the class is implementing. If an IPlugin interface is implemented, we add an instance of the current class to the list of valid plug-ins.

How to use the code?

Actually we neglected the "Program.cs" so far. We implemented everything we need to handle plug-ins but are not able to test it. Let's change this fact and open the file "Program.cs".

To our main method we add some code to retrieve the available ".dll" files and available plug-ins within them.

static void Main(string[] args)
{
    // Get list of Dll files within current directory.
    List<string> dllFiles = PluginUtils.GetDllList(Directory.GetCurrentDirectory());

    // Get plugins within found Dll files.
    List<IPlugin> plugins = PluginUtils.GetPlugins(dllFiles);
}

Here we are retrieving all ".dll" files within the current working directory and trying to get valid plug-ins within these.

To list the found plug-ins we implement another method within the "Program.cs" file:

/// <summary>
/// Shows a list of found plugins.
/// </summary>
/// <param name="plugins">List of IPlugin objects.</param>
private static void ShowPluginList(List<IPlugin> plugins)
{
    Console.WriteLine("Found plugins");
    Console.WriteLine("-----------------------------------------------------");
    int i = 0;
    foreach (IPlugin plugin in plugins)
        Console.WriteLine(string.Format("{0,2}) {1}", ++i, plugin.Name));
    Console.WriteLine(" x) Exit");
}

Here we are just building a small overview about found plug-ins. Now let's make this overview to a small menu, allowing us to call the appropriate plug-in. For this purpose, we move our focus again to the main method, to which the following code is added after retrieving the valid plug-ins.

...

string key = string.Empty;

// Show menu and run selected plugin.
while (key.Trim().ToLower() != "x")
{
    // Show a list of found plugins.
    ShowPluginList(plugins);
    Console.Write("--> ");
    key = Console.ReadLine();

    int pk;
    int.TryParse(key, out pk);
    Console.WriteLine();
    if (pk != 0)
        Console.WriteLine(((IPlugin)plugins[pk - 1]).Run());
    Console.WriteLine();
}

...

I think the code is quite self explaining. Until another value than "x" is read from the keyboard, we call the Run() method of the list entry and print its result value to the screen.

Conclusion

If you have everything set up and implemented correctly, you should now be able to run your solution and get the following result where you can choose the plug-in to run. (Note: The screenshot shows a second implemented plug-in, which is not part of the sources presented in this tutorial, but part of the sources available to download.)

Output

Points of Interest

  • Is there really a need of "DBB.Plugins.Ext"?
  • Yes, there is! This .dll file is the window to the world and the world's window to our application. You can implement the interface in both projects but the compiler will not know to which type to cast the object. So you will run into a CastTypeException if you do not use the way with the extra .dll file.

  • Why is the plug-in handling implemented in "DBB.PluginsTest"?
  • Well, you can also implement the whole handling in "DBB.Plugins.Ext". But then you enable plug-ins to have plug-pns to have plug-pns, ...

  • Is the plug-in support safe?
  • No! Plug-in support is unsafe! You cannot control what a plug-in does. It might be named like "Rename files" and format your harddisk after it has been called.

  • What do I have to provide to a developer who wants to create a plug-in?
  • Just provide the developer the "DBB.Plugins.Ext" .dll file and the description of your IPlugin interface.

History

  • May 03, 2012 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

HiDensity
CEO
Germany Germany
Developing software for more than 20 years.
 
Studies of Computer Sciences at Trier (Germany), Cambridge, MA (USA) and Berlin (Germany).
 
Graduated as B.Sc., M.Sc. and Ph.D.
Awarded by the University of Miami, FL (USA) with Ph.D. (h.c.).
 
Experienced in (in chronolgical order): Turbo-Pascal, C, C++, Visual Basic, JavaScript, PHP, COBOL(-ILE), CL(-ILE), Visual Basic .NET, RPG(-ILE), Java, C#.
Databases: DB2, MySQL, Oracle, PostgreSQL.
 
From 2008 to October, 2013 employed as Chief Technology Officer and Director Business Development.
 
Since October, 2013 CEO of own Software Developing company.
 
References: Audi, Free State of Bavaria, Bentley, City of Berlin (Germany), BMW, IBM Germany, Lufthansa, Olympus Medical Systems, Porsche, Volkswagen

Comments and Discussions

 
GeneralMy vote of 4 PinmemberOleg A.Lukin9-May-12 6:34 
GeneralRe: My vote of 4 PinmemberHiDensity9-May-12 11:13 
GeneralRe: My vote of 4 PinmemberDarchangel23-May-12 11:14 
GeneralRe: My vote of 4 PinmemberHiDensity25-May-12 5:21 
QuestionA word about security PinmemberGuillaume BOTTESI9-May-12 3:12 
AnswerRe: A word about security [modified] PinmemberHiDensity9-May-12 11:18 
Generalgreat job Pinmembercharlybones8-May-12 7:05 
GeneralRe: great job PinmemberHiDensity8-May-12 7:13 
GeneralMy vote of 5 Pinmembereinstein's brain8-May-12 1:51 
GeneralRe: My vote of 5 PinmemberHiDensity8-May-12 2:33 
Thank you for your feedback and vote. Smile | :)
SuggestionYou can simplify GetPlugins() a little PinmemberJared McGuire7-May-12 12:11 
GeneralRe: You can simplify GetPlugins() a little PinmemberHiDensity7-May-12 12:22 
SuggestionRe: You can simplify GetPlugins() a little Pinmemberbond.martini7-May-12 14:41 
GeneralRe: You can simplify GetPlugins() a little PinmemberHiDensity8-May-12 2:32 
QuestionExcellent article! PinmemberClavius Maximus7-May-12 10:59 
AnswerRe: Excellent article! PinmemberHiDensity7-May-12 11:05 
QuestionGreat article, very similar to what I once did... Pinmemberalcexhim7-May-12 7:27 
AnswerRe: Great article, very similar to what I once did... PinmemberPaul897-May-12 9:04 
GeneralRe: Great article, very similar to what I once did... Pinmemberalcexhim7-May-12 9:09 
GeneralRe: Great article, very similar to what I once did... PinmemberPaul897-May-12 9:24 
AnswerRe: Great article, very similar to what I once did... [modified] PinmemberHiDensity7-May-12 10:39 
GeneralMy vote of 4 PinmemberMember 23036055-May-12 12:24 
GeneralRe: My vote of 4 PinmemberHiDensity6-May-12 0:33 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150301.1 | Last Updated 4 May 2012
Article Copyright 2012 by HiDensity
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid