Click here to Skip to main content
15,886,797 members
Articles / Programming Languages / C#
Article

Plug-in Engine with Event Support

Rate me:
Please Sign up or sign in to vote.
4.58/5 (18 votes)
16 Mar 2006CPOL4 min read 55.4K   735   106   10
An article describing how to make a fully extensible plug-in manager that supports events from one plug-in to the other.

Introduction

Currently, I am working on creating an advanced multi-protocol IM program similar to GAIM or Trillian. However, I wanted to make the program completely extensible so that anyone could create a plug-in with a simple understanding of the application core. I looked at some of the other plug-in engines on CodeProject but couldn’t find any that did exactly what I wanted. In order for those engines to work, they needed to know specific information about all of its plug-ins, which I didn’t want to know. I wanted it so that anyone could create a plug-in without having to recompile the core at all. I started with the Dynamic Plug-in Engine by Bob Aman.

Changes to the Original Plug-in Engine

Loading Plug-ins

I wanted the plug-in developers to have other classes in their DLL besides the actual plug-in, and so I wanted some way to distinguish between classes that should be loaded and those that shouldn’t. The solution that I decided was best was to simply force all plug-ins to have an attribute, and I would check for the existence of that attribute before I saved the type as a valid plug-in.

Instantiating Plug-ins

I was running into problems with Aman’s engine because of his RemoteClassLoader that was running in a separate AppDomain. Whenever I tried to get any information about the classes and types I created, I was getting FileNotFoundExceptions because the calls I was making were trying to cross the AppDomain. Therefore, I decided that it was best to create the instance in the LocalClassLoader and use the remote one just to store all of the types of plug-ins that could be loaded.

The new LocalClassLoader.CreateInstance:

C#
public object CreateInstance(string typeName)
{
   string file;
   file  = remoteLoader.GetOwningAssembly(typeName);
   file = Path.Combine(PluginManager.pluginDirectory, file);
   ObjectHandle oh = Activator.CreateInstanceFrom(file, typeName);
   return oh.Unwrap();
}

The important method calls are GetOwningAssembly() and CreateInstanceFrom().

  • GetOwningAssembly() is what I replaced RemoteClassLoader’s CreateInstance with, and all it does is find the assembly that contains the given type name, and returns the name of the file that contains the assembly.
  • CreateInstanceFrom() is a standard system method that is used to create objects in the current AppDomain. This also includes any references so that there is no complicated crossing of the AppDomain.

Listening to Events from Plug-ins

Adding the Event Handlers

C#
private static void AddEventHandlers(object o)
{
 foreach (EventInfo ev in o.GetType().GetEvents())
 {
    ev.AddEventHandler(o, 
      Delegate.CreateDelegate(ev.EventHandlerType, 
      typeof(Core), "Event"));
 }
}

This method uses reflection to find all of the public events in o, and adds an event handler for each of them that goes to the method Event. The advantage of this is that all events will be processed by one method, so there can be, in theory, an infinite amount of different events that the core doesn’t have to know anything about. The only requirement of these events is that they have the right delegate syntax. The delegate that I defined for the events is:

C#
NotifyCore(object sender, string name, EventArgs e);

The sender and EventArgs are the same as a simple event handler, but the name parameter is where the real power of our plug-in engine happens, but I will go into that later.

Capturing the Events

Capturing the events is a trivial process because of the single method Event that listens to all the events thrown by any plug-in. The body of Event is:

C#
internal static void Event(object sender, string name, EventArgs e)
{
   foreach (CoreEventListener cel in listeners)
   {
      cel.onEvent(sender, name, e);
   }
}

As you can see, this does nothing but iterate through all of the CoreEventListeners, and calls a method called onEvent with the same parameters that the event threw. The important thing is to now describe what a CoreEventListener is. A CoreEventListener is a special type of plug-in that, as you might have guessed, listens to events thrown from the other plug-ins. When a new CoreEventListener is created, it is added to the List called listeners.

Re-throwing the Events

The events are thrown back to the CoreEventListeners in the onEvent method:

C#
public void onEvent(object sender, string Name, EventArgs e)
{
   if (sender != null && sender.GetType() 
                      == this.GetType()) return;
   foreach (MethodInfo mi in this.GetType().GetMethods())
   {
       if (mi.Name == Name)
        {
            mi.Invoke(this, new object[] { sender, 
               Convert.ChangeType(e, e.GetType()) });
       }
   }
}

The first thing this method does is check if this event came from the current CoreEventListeners, and, if so, return because listening to your own events could propose some problems. The foreach statement again uses Reflection to search for the method that has the same name as the event. That is the real magic of the Name parameter. You can put anything in there, and if someone created a method with the same name, it would “throw” the event to that class. Another interesting statement in this method is the Convert.ChangeType call. This allows for using subclasses of EventArgs to throw methods, and dynamically casts it to the correct type before it is sent so that the capturing method doesn’t have to cast it but can instead use the correct EventArgs in the method declaration.

Example

Included in the Plug-in Manager are two example plug-ins. These plug-ins emulate how easy it is to communicate using events. The plug-ins send a message event to the other and display the output on their form.

Conclusion

The Plug-in Engine start method has an Application.Run statement, so when you start it you need to close it. The reason I decided to do this is because it is a static class and it needs to stay open, so I decided it was best to do it this way. If you have any questions, suggestions, or comments, feel free to email me at rpgprog@gmail.com.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer Warner School of Education
United States United States
James is attending college at the University of Rochester working on two degrees a B.S. in Computer Science and a B.A. in Linguistics. He is currently working at the Warner School of Education working with PHP. James has been programming using Visual Basic since 1998 and recently he moved to the Realm of C# at the end of 2005.

Comments and Discussions

 
GeneralClosing The PluginManager Core Pin
James S. Taylor22-Aug-07 5:35
James S. Taylor22-Aug-07 5:35 
QuestionHow do I add this to my application? Pin
Haydeng17-Aug-07 12:33
Haydeng17-Aug-07 12:33 
GeneralTabbed plugin host application Pin
Dugr26-May-07 17:24
Dugr26-May-07 17:24 
GeneralRe: Tabbed plugin host application Pin
James J. Regan IV29-May-07 3:21
James J. Regan IV29-May-07 3:21 
GeneralRe: Tabbed plugin host application Pin
Dugr29-May-07 9:36
Dugr29-May-07 9:36 
GeneralSorry... Pin
Arch4ngel17-Mar-06 3:42
Arch4ngel17-Mar-06 3:42 
QuestionSource Code and Demo? Pin
Alessio.NET16-Mar-06 21:24
Alessio.NET16-Mar-06 21:24 
AnswerRe: Source Code and Demo? Pin
Mikael Wiberg16-Mar-06 22:40
Mikael Wiberg16-Mar-06 22:40 
GeneralRe: Source Code and Demo? Pin
Alessio.NET16-Mar-06 22:47
Alessio.NET16-Mar-06 22:47 
AnswerRe: Source Code and Demo? Pin
James J. Regan IV17-Mar-06 0:51
James J. Regan IV17-Mar-06 0:51 

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.