Creating a plugin-interface

  • Thread starter Thread starter Henrik Heimbuerger
  • Start date Start date
H

Henrik Heimbuerger

Hi!


I tried a lot now but I think I need some help. ;-)

What I would like to have is a plugin interface for one of my
applications. There should be an Interface (e.g. IPluginInterface) and
it should be possible to create assemblies with classes that implement
this interface.
At runtime, I would like to load all assemblies in a special directory
(say: "\plugins"), see if there are classes in them that implement
this interface and if so, get a reference to an instance of the class
that implements it.

What I managed to do is the following:
ObjectHandle oh = Activator.CreateInstanceFrom(file, classname);
IPluginInterface p = (IPluginInterface)(oh.Unwrap());
Console.WriteLine(p.getTestString());

That worked. But there are two downsides:
a) I have to know the name of the class. I would prefer if my
application wouldn't care about the name and just check whether the
class implements the interface.
b) Can I be sure that cast in the second line fails if and only if the
interface is not exactly the same as my IPluginInterface? Would it be
possible to create a plugin where this cast does not fail but one of
the following method invocations fail? By creating a new
IPluginInterface that is not compatible to my one for example?

Or do you think there may be better plugin interfaces? The examples in
the help often use MethodInfo.Invoke() for this but I don't like it. I
just want to use this interface as if the plugin were directly coded
in my application. No more IDispatch-handling please... ;-)


Thanks for your help,
Henrik
 
Take a look at the plug-in framework found at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/pluginframework.asp

I've used it successfully on an application requiring plug-ins to handle
various functions according to the ERP system we're communicating with. The
thing I like best about it is everything pertaining to the plug-in is stored
in either a Web.config file or an <AppName>.config file in XML format...the
application doesn't care what the plug-ins class name is so long as it is
stored in the config file.

I did have to make a few changes to the code found at that address as I'm
storing the fully-qualified filename for the plug-ins in the .config file.
Additionally, I had to modify the method for creating an instance of the
plug-in from the line
object plugObject =

Activator.CreateInstance(Type.GetType(node.Attributes["type"].Value));
to
object plugObject =
Activator.CreateInstanceFrom(fqPath, typeName).Unwrap();

I hope this helps some.

Allen
 
This sounds like a good time to use reflection. Using reflection you could
load the assembly and loop through each type looking for any class that
supports the IPlugin interface.

I created a little example. The solution consists of 3 projects (IPlugin,
Plugins, and Testing).


The IPlugin project (Class Library) consists of the following interface
definition:

namespace Testing
{
public interface IPlugin
{
string Name{get;}
}
}


The Plugins project (Class Library) references the IPlugin project and
defines the following classes:

namespace Testing
{
public class Plugin1 : IPlugin
{
public string Name
{
get{return "Plugin #1";}
}
}

public class Plugin2 : IPlugin
{
public string Name
{
get{return "Plugin #2";}
}
}

public class MiscClass
{
public string Name
{
get{return "Miscellaneous Class";}
}
}
}


The Testing project (Console Application) also references the IPlugin
project and defines the following class:
namespace Testing
{
public class Class1
{
public static void Main(string[] args)
{
string[] dlls =
Directory.GetFiles(Directory.GetCurrentDirectory() + @"\plugins\", "*.dll");
foreach(string dll in dlls)
{
Assembly asm = Assembly.LoadFile(dll);
foreach(Type type in asm.GetTypes())
{
Type iType = type.GetInterface("Testing.IPlugin");
if(iType!=null)
{
IPlugin plugin =
(IPlugin)Activator.CreateInstance(type);
Console.WriteLine(plugin.Name);
}
}
}
}
}
}


After the projects are compiled and the Plugins.dll is placed in the
\plugins\ directory beneath the compiled console app. Running the console
App (Ctrl+F5) should provide the following output:

Plugin #1
Plugin #2
Press any key to continue


Hope that helps.
 
On Tue, 9 Nov 2004 07:38:01 -0800, "AlBruAn"

Ok, took some hours to dig through this... ;-)

Thank you a lot for this link! It perfectly fits my scenario.
(Funny enough, my test classes are nearly exactly named as the ones in
this example! ;-) After loading it, I first wondered why the solution
didn't load and why I still saw my test solution!)

And it helped me to finally get what I want (I think so, by all
means).
I've used it successfully on an application requiring plug-ins to handle
various functions according to the ERP system we're communicating with. The
thing I like best about it is everything pertaining to the plug-in is stored
in either a Web.config file or an <AppName>.config file in XML format...the
application doesn't care what the plug-ins class name is so long as it is
stored in the config file.

I wonder: why use this XML-file? Just more installation work for
plugin developers and another source for errors, isn't it?

[...]
I hope this helps some.

It did!

Here is my solution. I would greatly appreciate if you would tell me
if you see something that could possibly be a source for errors later,
that could be improved or if you can explain to me why the example
above doesn't do it like this.

============ C# ============
public void LoadPlugin()
{
//create an array for my plugins
ArrayList myPlugins = new ArrayList();

//load all .dll-files in this directory as assemblies
foreach(String file in Directory.GetFiles("plugins\\", "*.dll"))
{
Assembly a = Assembly.LoadFile(Directory.GetCurrentDirectory()
+ "\\" + file);

//get the types in this assembly
Type[] types = a.GetTypes();

//find all classes that implement IPlugin
foreach(Type t in types)
{
Type[] matchingTypes = t.FindInterfaces(
new TypeFilter(MyInterfaceFilter),
Type.GetType("PluginInterface.IPlugin"));

// foreach(Type t2 in matchingTypes) //doesn't work currently
{
object plugObject = null;
try
{
//create an instance of this class
plugObject = Activator.CreateInstance(t);
myPlugins.Add((IPlugin)plugObject);

Console.WriteLine("Successfully loaded plugin from file: "
+file);
Console.WriteLine(" the class name is: " +
plugObject.ToString());
Console.WriteLine();
}
catch(Exception e)
{
Console.WriteLine("Error while loading plugin from file: " +
file);
Console.WriteLine(" the reason is: " + e.Message);
Console.WriteLine();
}
}
}
}

bool MyInterfaceFilter(Type typeObj, Object criteriaObj)
{
if(typeObj == criteriaObj)
return true;
else
return false;
}
============ C# ============

It nearly works, there are only two issues left:
- I don't like to get the type of my interface via
Type.GetType("PluginInterface.IPlugin") because
if have to change that line if I change the name
of the class. Reminds very much of interpreted
languages. But I did not find another way to get
a type object of a type. Any ideas? It would be
"PluginInterface.IPlugin.class" in Java...
- The line "foreach(Type t2 in matchingTypes)" doesn't
work right yet. If I comment it out, the program
works. The output is:

============ output =============
Successfully loaded plugin from file: plugins\TestPlugin.dll
the class name is: TestPlugin.PluginClass

Error while loading plugin from file: plugins\TotallyWrongPlugin.dll
the reason is: Cannot create an instance of an interface.

Error while loading plugin from file: plugins\TotallyWrongPlugin.dll
the reason is: Specified cast is not valid.
============ output =============


Henrik Heimbuerger
 
Hope that helps.

Great, that is exactly what I want!

Sorry for my other post, I worked on this in the last hours and forgot
to read the newsgroup again. ;-) Would have eased my work a lot if I
had...

But there is one little issue left. As I wrote in the other posting, I
do not like to reference the interface with a string. Is there any way
to check if class is in a way 'binary compatible' (?) with my
interface?
I'll give you an example:

namespace Testing
{
public interface IPlugin
{
string NoName{get;} //not compatible to the other
//Testing.IPlugin!!!
}

public class Plugin3 : IPlugin
{
public string NoName
{
get{return "Plugin #3";}
}
}
}

Plugin3 is definitely not compatible with my plugin interface. But
your code should read it (tested it only with my code, modified with
your descriptions), but the cast fails. Sure, the cast exception can
be catched, but it would be much nicer if I could check if the plugin
really implements *my* Testing.IPlugin and not just any interface that
is *called* "Testing.IPlugin". (Otherwise, I could simply load every
type I find and try to cast them...) Any ideas if this is possible?


Henrik Heimbuerger
 
I'm not sure this is really a problem. I just tried creating a new project
named Plugins2 (Class Library). This project, instead of referencing the
IPlugin.dll, created a new version of the Testing.IPlugin interface as
follows:

namespace Testing
{
public interface IPlugin
{
string NoName{get;}
}
}

I also created a class named Plugin3 as follows:

namespace Testing
{
public class Plugin3 : IPlugin
{
public string NoName
{
get{return "Plugin #3";}
}
}
}

I then placed Plugins2.dll into the \plugins\ directory and tried the
console app again. It threw an InvalidCastException.

So, when examing the Plugin3 type...

Type iType = type.GetInterface("Testing.IPlugin");

Plugin3 does actually support an interface named Testing.IPlugin, but when
the following line executes:

IPlugin plugin = (IPlugin)Activator.CreateInstance(type);

An InvalidCastException occurs because it is trying to explicitly cast it to
the IPlugin interface, which is referenced in the current assembly via
IPlugin.dll and not the IPlugin interface declared in the currently iterated
assembly (\plugins\Plugins2.dll).

At any rate, if you wanted to check to make sure that the interface, which
was loaded by name is the same IPlugin interface that is referenced in the
executing assembly, you could do so by changing one line:

Change the following if statement from:

if(iType!=null)
{
...
}

Change it to:

if(iType!=null && iType==typeof(IPlugin))
{
...
}

Hope that helps.
 
Hi Jason and Allen!


I want to thank you both for helping me and for giving that detailed
explanations. I wouldn't have managed to do this without you. I
consider my current solution as final, meaning that it works exactly
as I wanted it. See below for the full code, if you are interested.


[exact decription of my problem]

Yes, that is exactly what I meant!
At any rate, if you wanted to check to make sure that the interface, which
was loaded by name is the same IPlugin interface that is referenced in the
executing assembly, you could do so by changing one line:

Change the following if statement from:

if(iType!=null)
{
...
}

Change it to:

if(iType!=null && iType==typeof(IPlugin))
{
...
}

That's it! I just didn't get the idea that I could use the
'typeof'-operator for creating a Type-object.

The following is my complete solution for the problem. It works
exactly as I wanted in the beginning. Maybe others are interested in
this as well.


1. This is my plugin-interface:

========== Plugin-Interface ==========
namespace PluginInterface
{
public interface IPlugin
{
String getTestString();
}
}
========== Plugin-Interface ==========


2. A plugin is created by:
a) creating a new project (Class Library)
b) adding a reference to the .dll built in
step 1
c) writing an arbitrarily named class that
implements IPlugin

========== Example-Plugin ==========
public class MyPlugin : IPlugin
{
public String getTestString()
{
return("Hello, world!");
}
}
========== Example-Plugin ==========


3. The main application loads all plugins from the subdirectory
"plugins\" with the following code:

========== Plugin-Loader ==========
private ArrayList PluginLoader()
{
//create an array for my plugins
ArrayList myPlugins = new ArrayList();

//load all .dll-files in this directory as assemblies
foreach(String file in Directory.GetFiles("plugins\\", "*.dll"))
{
Assembly a = Assembly.LoadFile(
Directory.GetCurrentDirectory() + "\\" + file);

//iterate over all types in the assembly
foreach(Type t1 in a.GetTypes())
{
//retrieve all interfaces of the current type
foreach(Type t2 in t1.GetInterfaces())
{
//if the interface matches exactly the plugin-interface...
if(t2 == typeof(PluginInterface.IPlugin))
{
try
{
//...then create an instance of the class
object plugObject = Activator.CreateInstance(t1);
myPlugins.Add((IPlugin)plugObject);

Console.WriteLine(
"Successfully loaded plugin from file: " + file);
Console.WriteLine(
" the class name is: " + plugObject.ToString());
Console.WriteLine();
}
catch(Exception e)
{
Console.WriteLine(
"Error while loading plugin from file: " + file);
Console.WriteLine(
" the reason is: " + e.Message);
Console.WriteLine();
}
}
}
}
}
}
========== Plugin-Loader ==========

It returns an array of instances of classes that implement IPlugin.
In this case, the test code could look like:

========== Run plugins ==========
ArrayList myPlugins = LoadPlugins();

foreach(IPlugin plugin in myPlugins)
{
Console.WriteLine(plugin.getTestString());
}
========== Run plugins ==========

That's it. Simple, isn't it? ;-)


Once again: thanks for you help, Jason and Allen!


Henrik Heimbuerger
 
Back
Top