December 4, 2007

Creating a plug-in interface using C# and .Net Reflection

I finally had the excuse recently to look into creating my very own plug-in interface. A plug-in essentially allows a piece of software to load dll files (more pieces of code) that it knows virtually nothing about before runtime. I was very impressed at how simple this is using .Net and its reflection implementation. When I began to research this topic, I found all kinds of articles that show you how to achieve this in a particular way. I would later find out that the common way to go about it is very inefficient. The reason for this is that .Net's reflection implementation has some very inefficient routines. Reflection is sluggish by nature; the parts of reflection that are not so sluggish were given special optimization attention because they are commonly used by .Net. I won't go into much more detail on this because I am by no means an expert on the matter, but I will point you to some very good literature.

This article will explain some of the insides of reflection and explain which parts are inefficient and why:

http://msdn.microsoft.com/msdnmag/issues/05/07/Reflection/

This article is something of a quick-reference guide to which reflection methods you should watch out for:

http://msdn.microsoft.com/msdnmag/issues/05/07/Reflection/default.aspx?loc=&side=true#a

This article will show you the ''common" way of implementing a plug-in interface (this is the inefficient way, see below for the better solution):

http://www.codeproject.com/KB/cs/ayassemblyattributes.aspx?df=100&forumid=33248&exp=0&select=798913


Now for the good stuff! So like I said before, your host application needs to know virtually nothing about the plug-in it will be using. However, in order to achieve a simplified and clean implementation for both you and your plug-in implementers, you need to set up a standard that both parties will go by. To do this, you will need to create an interface. An interface will allow you as the host application to use a single implementation for every plug-in of a certain type. It will also give your plug-in implementers a code path into your application. Here is an example interface:

public interface Iplugin
{
//You can have a method such as Initialize
//that will pass the plugin an object
//that will give the plugin access to information
//and functionality on the host.
//This could be done by creating an interface
//that your host will implement
//which will contain methods/properties that
//your plug-ins are meant to access
//and then passing the host object into
//the initialize function.
//We do this in an Initialize method because an
//interface cannot define constructors.
void Initialize(someObject info);

//I like to have my plug-in give some sort of
//description that I can use for
//logging or to show to a user of my host application
//to help define what this
//plug-in will do
string GetDescriptor();

//all you need to do is define the methods that
//you will expect all plug-in
//implementations to implement
void DoMethod();
...
}


You will want to create a project by itself with just interfaces (and similar) so that you can give the dll to your implementers without having to give them any of your host implementation code. Your implementers will need to use this interface on their objects that provide plug-in functionality.

public class HelloWorldPlugin : Iplugin
{
someObject hostInfo;

public void Initialize(someObject info)
{
hostInfo = info;
}

public string GetDescriptor()
{
return "Host Displays Hello World";
}

public void DoMethod()
{
//use the access you have to the host via the hostInfo object
//to make something on the host happen
...
}
}


So now we have our interface for our plug-ins and we have a test plug-in that implements the interface. Now we need to figure out how to get that code from the dll into the running host application. This is done using reflection. You will somehow need to let your software know where your plug-in or plug-ins are at. This can be done by asking the user to enter a location, passing in the location in a config file, having a default plug-in location, etc. Once we have the location of the plug-in, we will use reflection to load the dll and read in the information about it. Another name for your dll is an Assembly. The reflection APIs are in System.Reflection.

Most articles you find on plug-ins will have you load the assembly and get back a list of all the types inside the assembly. They would then have you loop through each type and check if it implements your interface. If it does, you would then continue to create an instance of that type and start using the plug-in. This is where the inefficiencies come in. You can review the articles at the links above to see an example codeproject article showing you this inefficient method as well as an article from MSDN explaining why you should not do it that way.

To keep from having to get all of the types and loop through them, we are going to have our assembly give us a list of types inside of it that implement our interface. This is done using attributes. Using the attribute technique, you will require your implementers to add an attribute to their AssemblyInfo.cs file within their project's properties folder. With this attribute, your implementers can tell you which types in their assembly are implementing your Iplugin interface. By doing this we are trusting that the user will pass us the correct information. This means we should probably verify that the types they pass really do implement your interface before we actually start using it.

Since we will be using the attribute technique to make a more efficient plug-in interface, we will need to define our own attribute. It is actually very simple to do. In the same file that you create your Iplugin interface in, add this class.


[AttributeUsage(AttributeTargets.Assembly, AllowMultiple=true)]
public class PluginAttribute : System.Attribute
{
public readonly Type ImplementingType;

public PluginAttribute (Type implementingType)
{
ImplementingType = implementingType;
}
}


Now your implementers will need to add this line to their AssemblyInfo.cs file.

[assembly: PluginAttribute (typeof(HelloWorldPlugin))]


Your implementer will include one of those declarations per plug-in that they provide, replacing HelloWorldPlugin with whatever the types name is. Now when you load the plugin from your host application, the assembly will automatically contain these attributes without having to ask for a list of types that you will loop through (the inefficient part). Now lets look at the code that will actually load the plugin into the host application so that we can use it.


Assembly asm = Assembly.LoadFile(dllPath);

PluginAttribute [] typeAttribs = (PluginAttribute []) asm.GetCustomAttributes(typeof(PluginAttribute ), false);

foreach (PluginAttribute attr in typeAttribs)
{
Iplugin methodInstance = (Iplugin)Activator.CreateInstance(attr.ImplementingType);

//methodInstance is an object of your plugin type!
//now just do whatever you want with your plugin.
//perhaps store it somewhere so you can use it from now on.

methodInstance.Initialize(something);
methodInstance.DoMethod();
}


Dont forget, you might want to make sure the type that you got from your attribute actually implements your interface before you try to cast it as that type. Something bad will happen if your implementer passed you a type that does not actually implement your interface. The best thing to do would probably be to wrap your code with try{}catch{} just in case. I like to wrap everything with try/catch and then wrap my foreach loop with try/catch as well. This way if just one type was wrong, you can still use the others, and you will still be okay if there was a problem loading the assembly.

Have fun plugging in!

No comments: