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
{
string Name { get; }
string Version { get; }
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
{
private string DoSomething()
{
return string.Format("{0} - Version: {1} -> {2}", Name, Version, "DoSomething()");
}
#region IPlugin members.
public string Name
{
get { return "TestPlugin1.1"; }
}
public string Version
{
get { return "1.0.0.0"; }
}
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.
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:
public static List<IPlugin> GetPlugins(List<string> dllFiles)
{
List<IPlugin> retVal = new List<IPlugin>();
foreach (string dll in dllFiles)
{
try
{
Assembly assembly = Assembly.LoadFile(dll);
Type[] types = assembly.GetTypes();
foreach (Type cls in types)
{
if (cls.IsPublic)
{
Type[] interfaces = cls.GetInterfaces();
foreach (Type iface in interfaces)
{
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)
{
List<string> dllFiles = PluginUtils.GetDllList(Directory.GetCurrentDirectory());
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:
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;
while (key.Trim().ToLower() != "x")
{
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.)
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.
Developing software since 1991.
Graduated as B.Sc., M.Sc. and Ph.D.
Experienced in (in chronological 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.