Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Pluggable and Plottable Objects

0.00/5 (No votes)
1 Jan 2010 1  
How to use Reflection to plug in unknown components with predefined behaviour.

Introduction

Nowadays, the idea of pluggable controls does not seem new, but back in 2002, it did. At that time, one of my students (Denis Soroka), after learning some basics of .NET Framework (God save Anders Hejlsberg and his amazing skill of creating wonders), quickly realized the advantages of using plug-ins. By the way, I am not going nuts with hypocycloids (some may think I am). This is a different article (pure coincidence).

Background

Imagine a truck delivering a bunch of pluggable DLLs and unloading them into a folder specified by your application. Let the folder name be Plugins. Then, imagine a FileSystemWatcher object monitoring the activity inside this folder and notifying your app about all the changes. Lastly, imagine an algorithm that distinguishes proper (or desired) DLLs from others (foreign ones) and turning in the desired functionality, i.e., creating unknown objects with predefined behaviour and launching its methods and property-form at run time.

This scenario was implemented by Denis Soroka in no time (based on my task of creating several controls using the Graph class developed earlier). Here, I am going to discuss the application (now used in one of the C# courses) and I will try to explain the technique of launching unknown objects and making them work (do the job you want them to do) at run time.

Using the Code

Launch the program. Try to click the lines in a list on the right. Then, without stopping the program, move all (or any) of the DLLs (you will find in a folder Plugins) to some other place in your file system. Watch the behaviour of the program. Bring the DLLs back to the Plugins folder. Now, you know what I want to discuss.

Solution Structure

Let the predefined (stipulated) behaviour of the would be plugged-in modules be described in an IPlottable interface. It serves to define the rules of the game, that is: showing different plots using the Graph class. It was written in 1987 as an exercise in C++. Now, it is revived as a simple C# class.

public interface IPlottable 
{
  PointF[] GetPoints();
  void ShowProperty();
  void DestroyProperty();
  string Name { get; }
  string Caption { get; }
  string Unit { get; }
  event Action ChangeProperty;
}

These requirements seem enough to render a one-curve chart. This code should be placed in a separate project of type Class Library so that any other project in our solution might get the reference to the IPlottable interface and thus obey the rules of the game.

public class Aperiodic : IPlottable
{
  // Data, Properties & Methods specific
  // to the Aperiodic transient curve (plot)
  // IPlottable Implementation
}

The logic of observing the Plugins folder and interacting with the newcomers should be implemented in a main project. Let it reside in the following class:

public partial class FormMain : Form
{
  // Data
  string pluginPath, pluginName;
  FileSystemWatcher watcher;
  Dictionary<string, Type> plots;
  IPlottable plot;
 
  // Methods
  public FormMain()
  {
     pluginPath = FindFolder("Plugins");
     plots = new Dictionary<string, Type>();
     InitializeComponent();
  }

  // Some Other Methods
}

Let's see the code of a method that discerns the new DLLs (plug-ins) in a folder being watched by FileSystemWatcher. Here, plot is currently the active plug-in. listDll is a ListBox to show the names of all plug-ins found in a folder, and plots is a collection to remember the would be plugged objects. The latter stores the pairs of type <string, Type>.

void FillDllList()
{
  if (plot != null)
     plot.DestroyProperty();
  pluginName = null;
  listDll.BeginUpdate();
  listDll.Items.Clear();
  plots.Clear();

  foreach (string file in Directory.GetFiles(pluginPath))
  {
     if (Path.GetExtension(file) != ".dll")
       continue;

   Assembly a = Assembly.LoadFrom(file);
   foreach (Type type in a.GetTypes())
   {
    if (type.GetInterface("IPlottable") != null)
    {
     plots.Add(type.Name, type);
     listDll.Items.Add(type.Name);
    }
   }
  }
  ResetPlot();
  listDll.EndUpdate();
}

The things to pay attention to are:

Assembly.LoadFrom(file)
a.GetTypes()
type.GetInterface("IPlottable")

They are examples of how to use Reflection (the famous .NET Framework instrument to investigate the unknown reality).

Note that you may use another (or rather additional) indicator to distinguish a proper DLL from others. Besides testing for the presence of the IPlottable interface, you may test one (or many) Custom Attributes that a DLL provider may have set to its plug-in.

It may be done in (with the code of) the file AssemblyInfo.cs. You have such a file in any new .NET project but probably never use it. I offer my apology to those who knew this feature and frequently used it. I often delete the file AssemblyInfo.cs as it is never used. Here is an example of testing for Custom Attributes. This fragment might be inserted into the FillDllList method.

object[] attr = a.GetCustomAttributes(true);
if (attr.Length < 13)
   continue;
string descr = null;
foreach (object o in attr)
{
   AssemblyDescriptionAttribute da = o as AssemblyDescriptionAttribute;
   if (da != null)
   {
    descr = da.Description;
    break;
   }
}
if (descr == null || descr != "PlotPlugin") // Some foreign assembly
   continue;

Well, speaking of the FileSystemWatcher object, there is not much to say. The logic is straightforward.

void SetWatcher()
{
  watcher = new FileSystemWatcher 
  {
    Path = pluginPath,
    EnableRaisingEvents = true,
    SynchronizingObject = this
  };
  watcher.Changed += OnWatcherChanged;
  watcher.Created += OnWatcherChanged;
  watcher.Deleted += OnWatcherChanged;
  watcher.Renamed += OnWatcherChanged;
}

We shortcut all the events generated by FileSystemWatcher to the only method OnWatcherChanged. The important setting here is: watcher.SynchronizingObject = this. It will help you to debug your project so that you don't have to use the Invoke/BeginInvoke scenario.

I believe you will find and read more about COM-objects related whims and troubles (implicitly invoked threads etc.) here in the huge CodeProject library.

Points of interest

I want you to pay attention to the way you start the unknown object. This is accomplished with the following code fragment:

Type type = plots[pluginName];

if (plot != null)
    plot.DestroyProperty();

plot = type.GetConstructor(Type.EmptyTypes).Invoke(null) as IPlottable;
plotControl.SetGraph(plot);
plot.ShowProperty();
plot.ChangeProperty += delegate() { plotControl.SetGraph(plot); };

Again, the trick is in the code that uses Reflection:

plot = type.GetConstructor(Type.EmptyTypes).Invoke(null) as IPlottable;

I wish you good luck and happy coding!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here