Click here to Skip to main content
13,089,246 members (66,214 online)
Click here to Skip to main content
Add your own
alternative version


119 bookmarked
Posted 19 Jan 2003


, 19 Jan 2003
Rate this:
Please Sign up or sign in to vote.
PluginManager: plug-in automation

What is this?

PluginManager is an automated plug-in handling system. It consists of 2 parts, namely the Proxy/Interface generator (which is responsible for making wrapper proxy classes and interfaces) and the PluginManager (which is responsible for loading/unloading the plug-ins and the AppDomain).

Why do I need this?

Suppose you have a persistent object that cannot destroyed (e.g. a network connection), but you also need provide loadable and unloadable plug-ins that will consume events and invoke methods from the persistent object. Finally, this will all need to run in the SAME program. This is an important consideration as allowing more than one process, the implantation could be easily done with a webservice and making consumer "plug-ins" for it. BUT we don't want that, do we?

Proxy/Interface generator

You will use this application to create a proxy classes and interfaces for your desired object to be consumed. Please note that this object has to inherit from MarshalByRefObject. Just one click of a button and a new assembly will be compiled and be ready to use. For the demo, the source of the output looks like:

namespace leppie.Plugins.Interface
   public class PluginController : leppie.Plugins.Manager.BaseController
      public PluginController(leppie.Plugins.ChatSimulator arg0) : 
   public class ChatSimulatorProxy : System.MarshalByRefObject, 
                   leppie.Plugins.Manager.IProxy, IChatSimulator
      private leppie.Plugins.ChatSimulator _obj;
      public ChatSimulatorProxy(leppie.Plugins.ChatSimulator obj)
         this._obj = obj;
         System.Reflection.EventInfo[] events = 
         for (int i = 0; (events.Length > i); i = (i + 1))
            System.Reflection.EventInfo evt = events[i];
                this, ("_" + evt.Name)));
      public event leppie.Plugins.MessageEventHandler Message;
      public override object InitializeLifetimeService()
         return null;
      public void Close()
         System.Reflection.EventInfo[] events = 
         for (int i = 0; (events.Length > i); i = (i + 1))
            System.Reflection.EventInfo evt = events[i];
               this, ("_" + evt.Name)));
      public void _Message(object sender, leppie.Plugins.MessageEventArgs e)
            if ((this.Message != null))
               this.Message(sender, e);
         catch (System.AppDomainUnloadedException ex)
      public void Connect()
      public void Disconnect()
      public void SendMessage(string user, string message)
         this._obj.SendMessage( user,  message);
   public interface IChatSimulator
      event leppie.Plugins.MessageEventHandler Message;
      void Connect();
      void Disconnect();
      void SendMessage(string user, string message);
   public interface IPlugin : leppie.Plugins.Manager.IBasePlugin
      void Load(IChatSimulator arg0);

This is all that is required to use PluginManager. Most of the code in the proxy/interface maker is just boring CodeDom and reflection. I have tried to make it output VB.NET and associated assemblies, but unfortunately my VB.NET knowledge is NULL. For now, you can just consume the generated assembly (which will work from VB.NET).

Implementation of the generated assembly

In the main application you will need to create an instance of the generated PluginController class. This class allows for the loading/unloading of the "Plug-ins" AppDomain. This in turn calls the PluginManager that takes care of the rest. Example:

chat = new ChatSimulator();
pc = new PluginController(chat);

That is it!

Implementation of the plug-in

There are 2 rules for making a plug-in. Firstly, it must have the Plugin attribute applied to the plug-in class. Secondly, you will need to implement the IPlugin interface (which has 2 methods Load and Unload from IBasePlugin). Note how, interface that was created allows you to just make one change to an existing "fixed" plug-in (replace ChatSimulator reference with an IChatSimulator reference). Example:

public class TestPlugin : IPlugin
   private IChatSimulator chat;

   public void Load(IChatSimulator chat)
      Console.WriteLine("Loading plugin: " + this); = chat; += new MessageEventHandler(MessageReceived);

   private void MessageReceived(object sender, MessageEventArgs e)
      if (e.User == "Console")
         Console.WriteLine("Event in: {0}, MSG: {1}, USER: {2}, SENDER: {3}",
            this.GetType().Name, e.Message, e.User, sender.GetType().Name);
         chat.SendMessage(this.GetType().Name, "pong: " + e.Message);

   public void Unload()
   { -= new MessageEventHandler(MessageReceived);
      Console.WriteLine("Unloading plugin: " + this);

Be sure to register event listeners in Load and deregister them in the Unload method (although not strictly required).

Points of interest

While doing this I was referred to the SOAPsuds tool. Unfortunately, this tool does not create the correct code I need, thus the need to make custom generator. The problem is that events and delegates are just wrapped, but not relayed. Thus in my case (running in one process) this will STILL load the plug-in assembly, defeating the point of isolation.

While the remoting part of this is totally transparent, its in fact being used. For example: Passing a MarshalByRefObject-derived class as a parameter to an object's method in another AppDomain will automagically (yes, that is correct) convert the passed object to an ObjRef. This can be seen in the debugger.

Another problem aroused to create an object in another AppDomain. You will need to use an ObjectHandle, that is for use with MarshalByObjectValue classes. This was in fact the confusing part as this is the ONLY way to instantiate an object (whether by value or by reference) in another AppDomain. I wish there were more remoting examples in MSDN.

I have also chosen to load the plug-in assemblies via memory, meaning that the plug-in assemblies can be deleted/overwritten. Thus making it easy to attach the debugger to the process and then build new versions on the plug-ins via the "Debug > Start new instance" right click menu. All that's required is to reload the PluginManager via the PluginController or set PluginController.EnableFileSystemWatcher to true. NOTE: make sure the plug-in loads and behaves before doing this.

Finally, some usage notes:

  • Look at the project configuration for the TestPlugin in the DemoApp. Please make the output of the plug-in go into a Plugins subdirectory in the DemoApp debug/release folder.
  • Set all non-GAC assembly references' CopyLocal property to false. THIS is important. The project will not build. Also add supported assemblies to the DemoApp debug/release folder.
  • Look out for exceptions that can be caused when serializing classes, particularly EventArgs type classes. Mark these classes as Serializable or inherit from MarshalByRefObject.

PluginController outline (for example)

public class PluginController : BaseController
  public PluginController(ChatSimulator arg0); 
  public void LoadPlugin(string filename);
  public void LoadPluginManager();
  public void UnloadPlugin(string typename);
  public void UnloadPluginManager();
  public bool EnableFileSystemWatcher { get; set; }
  public string[] PluginNames { get; } 

Thanks to Lutz Roeder's Reflector.


When running the DemoApp, note in the output window how the AppDomains are loaded/unloaded.

My personal implementation of this is for the Sharkbite IRC library (with some slight modification). I had it running the weekend without any problems on working plug-ins. This actually passes 2 objects to the PluginController. So it seems to work for multiple exposed classes.

Known problems/warnings

  • Unloading/reloading from a proxied event is not recommended and will throw an exception that is impossible to catch, except from the class being proxied (i.o.w. the event invoker). See CL code for more details. I recommend using the FileSystemWatcher.
  • Classes that are being proxied are done so at "root" level, only so interfaces are not generated for contained classes. If you need to use events from these, add the class to be proxied as well, or perhaps re-think what classes are being exposed to the plug-in. In my case with the Sharkbite IRC library, I had a single Connection class with 2 containing classes (Sender, Listener). It was in my case better to pass both a Sender and a Listener object into the plug-in. Anyone interested in a copy of the Sharkbite implementation can E-mail me.


  • (01/20) - Added a WinDemoApp to example plug-in
  • (01/20) - Added release compiled WinDemoApp/DemoApp (TIP: for a nice demo, run the app (Win or CL) and delete the plug-in, then restore it from the Recycle Bin, you will see how it gets loaded/unloaded).
  • (01/21) - Updated the source with more comments and event now uses the .NET style (object, EventArgs) events.
  • (01/21) - Added some extra details throughout the article and fixed dumb IntelliSense-less (read class names) errors.


This article, along with any associated source code and files, is licensed under The BSD License


About the Author

Software Developer
South Africa South Africa
No Biography provided

You may also be interested in...

Comments and Discussions

Generallink to article explaining visually what actually happens Pin
korsuas16-May-08 2:14
memberkorsuas16-May-08 2:14 
GeneralRe: link to article explaining visually what actually happens Pin
leppie16-May-08 5:24
member leppie 16-May-08 5:24 
GeneralThanks Pin
TheChronicKP22-Jul-05 21:57
sussTheChronicKP22-Jul-05 21:57 
Generalneed help Pin
miliraj127-Mar-04 21:30
membermiliraj127-Mar-04 21:30 
GeneralAssembly.Unload Pin
Marc Clifton1-Feb-04 3:13
editorMarc Clifton1-Feb-04 3:13 
GeneralRe: Assembly.Unload Pin
tditiecher19-Feb-04 12:04
membertditiecher19-Feb-04 12:04 
GeneralNice work Pin
tec-behind27-Dec-03 13:50
membertec-behind27-Dec-03 13:50 
GeneralRe: Nice work Pin
leppie27-Dec-03 18:58
memberleppie27-Dec-03 18:58 
GeneralConfusing sample code Pin
Richard Hein23-Feb-03 14:04
memberRichard Hein23-Feb-03 14:04 
GeneralMessaging Pin
Marc Clifton20-Jan-03 7:57
memberMarc Clifton20-Jan-03 7:57 
GeneralRe: Messaging Pin
leppie20-Jan-03 10:59
memberleppie20-Jan-03 10:59 
GeneralBut what if.... Pin
Chopper20-Jan-03 4:52
memberChopper20-Jan-03 4:52 
GeneralRe: But what if.... Pin
leppie20-Jan-03 6:15
memberleppie20-Jan-03 6:15 
GeneralRe: But what if.... Pin
Chopper20-Jan-03 6:23
memberChopper20-Jan-03 6:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170813.1 | Last Updated 20 Jan 2003
Article Copyright 2003 by leppie
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid