|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionYour application may gain a lot of flexibility if you allow yourself and others to add new functionality after deployment. This can be done if you enable your code to host new components that are not yet known at design time, sometimes referred to as Plugins. In this article, you learn about the very basics of how late binding in C# can do the job, by creating the most primitive skeleton of a plugin and its hosting application. The Plan
Let's get clear about our goal: we want to write a C# application that invokes operations or uses attributes of components it does not yet know about at the time when the application is written. To be more precise, our app of course has to know what it expects from the plugin component, otherwise it would not know what to do with it in the first place. We will express this expectation in the form of a C# interface. We must also be more precise in what we mean when we talk about the plugin being a component, which is a term taken from UML or COM. The .NET platform calls physical artifacts like executable applications and reusable libraries assemblies. Our plugin will be such an assembly and contain at least one C# class that implements the interface that our application relies on. Finally, this plugin interface must be available to both, the application as well as the plugin. To enable creating and building plugins without depending on the application itself, we will put the interface also into a separate assembly. The figure shows the static structure of our setup (for the sake of generality showing a second plugin), eventually leading to three assemblies:
To Know or Not to KnowWhat does it mean from a technical point of view that our application will not know the concrete plugin at design time, but is able to invoke its features at runtime? The keyword here is what is referred to as late binding. Instead of statically compiling the program with the known class type of the plugin implementation, we dynamically load the plugin assembly and instantiate the plugin class. .NET provides numerous variations to perform both of these tasks. We will use one of the simplest and most straightforward forms, where we specify the plugin assembly through its file path and the class to be instantiated from it through its class name. This can be done through a single call to: System.Activator.CreateInstanceFrom( string assemblyFilepath, string typeName );
The object returned from this method is actually only a handle to the instantiated type that still needs to be unwrapped to reveal the actual object, but we will see this further below. Let's start with the hands-on job in Visual Studio 2005 or its Visual C# Express variant. Projects Setup
We create a new solution called Since the interface project will always be shared between application and plugins, we can add this dependency manually. It is worth noting in the given context that this is a static reference, well known at design time. Therefore, we can already add the Next we add the types. Add an interface to the Making It WorkNow that the projects have been set up, we will fill the classes and interface with life. The simplest functionality a plugin could possibly provide to its host is to reveal its name. Of course, much more complex tasks can be outsourced through a plugin mechanism, but the name query is enough to show the fundamental mechanism. Therefore, we define the interface type in the namespace PluginInterface
{
public interface IPluginInfo
{
string name { get; }
}
}
The implementing plugin in the using PluginInterface;
namespace PluginX
{
public class PluginInfo : IPluginInfo
{
public string name
{
get
{
return "Plugin X";
}
}
}
}
The last piece is now to late-bind the plugin assembly at runtime into the application and to instantiate the corresponding type. As mentioned before, we use the simple approach of matching names. Especially for the plugin's class name, this may look like a cheat. However, it is certainly possible to require your plugin suppliers to follow a certain naming convention for the plugin's namespace and the contained class. If this is not an option, .NET provides quite a number of advanced ways to search a loaded assembly for a type. E.g. it would be possible to look for a class that actually implements the known using System.Reflection;
using System.Runtime.Remoting;
using PluginInterface;
namespace PluginTest
{
class Program
{
static void Main( string[] args )
{
ObjectHandle oHandle = Activator.CreateInstanceFrom
( "..\\..\\..\\PluginX\\bin\\Release\\PluginX.dll",
"PluginX.PluginInfo" );
IPluginInfo pluginInfo = oHandle.Unwrap() as IPluginInfo;
Console.WriteLine( "Loaded plugin's name is: " + pluginInfo.name );
}
}
}
History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||