Jaa


C# 2.0: Loading plugins at run-time using late binding

I was working on a home project for creating a motion-detector that works using a webcam. Instead of using a baby-monitor I am planning to run this on an old computer. I'll point the web-cam to my daugthers' crib and the program would monitor both sound and her motions and ring an alarm in case she moves or makes any noise.

While working on this I wrote some code to support plugins to the application so that I can choose and use any motion-detection algorithm that I want. Some time back there was some questions on our internal DL about using late-binding to load and execute code. So I thought I'd blog about the code I used for the plugin loading and executing.

I used the following steps for this

  • List all dlls is a directory (plugins directory)
  • Load all assemblies from this dir
  • Iterate through all the types in the assembly and look if the type implements the plugin interface
  • Create an instance of the type that implement the interface and store it in a List

To do all of the above I wrote the following generic method that given any interface and a folder name can create a List of instances of all types that implement the plugin interface.

 using System.Reflection;using System.Collections.Generic;public List<T> GetPlugins<T>(string folder){    string[] files = Directory.GetFiles(folder, "*.dll");    List<T> tList = new List<T>();    Debug.Assert(typeof(T).IsInterface);     foreach (string file in files)     {        try         {            Assembly assembly = Assembly.LoadFile(file);            foreach (Type type in assembly.GetTypes())             {                if (!type.IsClass || type.IsNotPublic) continue;                Type[] interfaces = type.GetInterfaces();                if (((IList)interfaces).Contains(typeof(T)))                 {                    object obj = Activator.CreateInstance(type);                    T t = (T)obj;                    tList.Add(t);                }            }         }        catch (Exception ex)        {            LogError(ex);        }    }    return tList; }

With this method I can write code to show the list of plugins with their description in a menu or list control as follows

 string exeName = Application.ExecutablePath;string folder = Path.Combine(Path.GetDirectoryName(exeName), "Plugins");List <IMotionDetector> list = GetPlugins<IMotionDetector >(folder);  m_listPlugin.Items.Clear(); // list boxforeach (IMotionDetector detector in list){    string name = detector.GetPluginName();    string desc = detector.GetPluginDescription();    string str = string.Format("{0}, {1}, {2}",    detector.GetType().FullName, name, desc);    m_listPlugin.Items.Add(str); }

The interface I used is defined as

 using System;using System.Drawing;namespace MotionDetector{    // Event fired by the plugin on detecting motion    public delegate void MotionEvent(object sender, EventArgs args);    public interface IMotionDetector    {        // Plugin information - Since plugin det        string GetPluginName();        string GetPluginDescription();         void SetImage(Bitmap bitmap);        void Reset();        event MotionEvent Motion;    }}

Since I used generics I marked the blog title with C#2.0.

Comments

  • Anonymous
    November 14, 2005
    " I'll point the web-cam to my daugthers' crib and the program would monitor both sound and her motions and ring an alarm in case she moves or makes any noise"

    I take it you don't have any kids yet. Your daughter is going to move and make noise a lot at night.

    It's normal. Nothing to ring an alarm about.

  • Anonymous
    November 14, 2005
    :) I do have a 11 month old daughter

  • Anonymous
    December 20, 2005
    Hi,

    I followed your instructions and had some problems. The main problem is the following:

    Let's say the main program is called TestApp; the namespace is then TestApp. I then created a dll-project, called NCBI; the namespace in this project is NCBI.

    In the NCBI namepace there is a class called Parser. In the main programm (TestApp) I want to instantiate an object of type NCBI.Parser. This works fine with

    Object obj = Activator.CreateInstance(type);

    The difficulty is to cast obj into NCBI.Parser, because TestApp does not know this class.

    I also tried to define an interface, which describes some properties and methods of the NCBI.Parser class.

    If the interface is located in TestApp, casting is not possible, because NCBI.Parser was not told to implement the TestApp.IParser interface.

    On the other hand, if the IParser interface is defined in the NCBI project as NCBI.IParser, the interface is not known in the TestApp.

    Do you have any suggestions?

    Regards,

    Ruediger

  • Anonymous
    December 21, 2005
    This whole sample relies on using interfaces only. There are two approaches you can take
    1. Have the interface defined as public in the TestApp. In the plugin add the TestApp as reference and use it

    2. Create a seperate assembly dll named something like TestAppPlugins.dll which contains the interface. Add reference to this interface from both the plugin and the TestApp.

    Let me know if you hit any issues

  • Anonymous
    February 05, 2006
    I hit a build error in GetPlugins. In the method GetPlugins I had to change these lines:
    Type[] interfaces = type.GetInterfaces();
    if (((IList)interfaces)...
    To:
    Type[] interfaces = type.GetInterfaces();
    if (((IList<Type>)interfaces)...
    Then everything worked well. Thanks for the helpful article.

  • Anonymous
    February 08, 2006
    Sometime back I had posted about writing applications that can load plugins using late binding. Users...

  • Anonymous
    March 08, 2006
    This intrigues me, do you have any working examples (simple ones that dont require a web cam) or more verbose information?  Such as, is the listed code all in seperate apps? dlls?  a single app?

  • Anonymous
    March 08, 2006
    I couldn't locate a ready made project/sln to share out :( I've used this in production code as well as multiple home projects though. I suggest you just create a simple class-lirary project and derive a class in it from say ICollection. Write another exe project and paste the code and point it to the folder containing the dll with
    List<ICollection> list = GetPlugins<ICollecion>(folder);

  • Anonymous
    March 09, 2006
    The comment has been removed

  • Anonymous
    March 09, 2006
    never mind, I got it figured out...

    Thanx for this article!!

  • Anonymous
    March 09, 2006
    I have a question about loading assemblies dynamically, such as is described here.  How would you unload the assembly, or would it even be necessary?

    Perhaps an example of what I need to do might help.

    Think of a TCP/IP listener.  Every incoming connection would be a new thread, and each thread would then load a specific DLL (plugin) based on it's need.  (various shipping carrier plugins, one for UPS, one for FedEx, one for DHL, etc...)

    Each thread would have to load it's own copy of the DLL because I'm not quite sure how to go about making something like this thread safe...

    So when the connection is terminated, I assume I'd have to unload the DLL, yes?

    And is there any problems with loading the same DLL (plugin) multiple times in different threads?

    Thanx!

  • Anonymous
    March 10, 2006
    You cannot unload managed dlls. You can however load dlls into AppDomains and tear-down the whole AppDomain.

    However, for you scenario there is no need for loading/unloading dlls. Dlls have nothing to do with thread-safety. You'd want make your classes thread safe and create seperate instances of these classes from each of your threads...

  • Anonymous
    March 14, 2006
    hi thanks for ur code. i manged to run the application. but i dont how to play a sound when the motion is detected. can you tell me where exactly it goes when motion is detected and where should i put the code of sound when amotion is detected

    regards
    Jaydeep

  • Anonymous
    March 14, 2006
    I also have been trying to implement your code in my personal project, I am having some problems when it comes to loading the actual assembly.

    are you simply having instances of the interfaces or the objects that implement the interfaces as well?

    I see a lot of people have figured it out, but im still having some problems, any help is apreciated.

    Best Regards,
    Alexandre Brisebois

  • Anonymous
    March 14, 2006
    The comment has been removed

  • Anonymous
    May 08, 2006
    i thank you to your effort in provide the sociaty enviroment with good tools and experiance
     
    taamneh

  • Anonymous
    August 30, 2006
    Great. Good, very useful piece of code. Thanks buddy.

  • Anonymous
    August 30, 2006
    I managed to run your code. Thanks for it!

    I have another problem:

    Say there are 10 different types in an assembly called "A". (A few types are derived from other types and interfaces)
    And another 10 different types in an assembly called "B". (A few types are derived from other types and interfaces like above)

    Both assemblies contain the same blueprint(class names, method names their method parameters and return types) BUT differ in their implementation("A" has SQL server db stuff and "B" has Oracle server db stuff).

    How can I just create instances of the types in "A" or "B" without using Activator.CreateInstance(type). I would at design time know the types and methods I need but need only the flexibility of swapping between dll "A" and "B".

    Can this be done in some way?  Thanks in advance for your answer.

  • Anonymous
    August 30, 2006
    Great job Abhinaba.  I found this really useful.

  • Anonymous
    August 30, 2006
    Gangesh, check out http://blogs.msdn.com/abhinaba/archive/2005/11/30/498278.aspx. Even though the title does not match your description but essentially it does what you are looking for...

  • Anonymous
    September 05, 2006
    The comment has been removed

  • Anonymous
    September 05, 2006
    Gangesh, I don't think I can help you with debugging this with just this much info. Could be that all the dependent assemblies are not present in the assembly look up paths. But it could be any other think as well....

  • Anonymous
    September 05, 2006
    May be some permission issue since I am running "Activator.CreateInstance" from the web server ?

  • Anonymous
    September 05, 2006
    Abhinaba,  

    I copied the Interface(base class) into both the dlls and the calling application (web app) thinking it could be something to do with the path stuff as I had the interface in a sepate dll.

    Abhinaba, Sorry to have bombarded with these questions. I will figure it out, I thought that it may be something else in code that needed to be done with Activator.CreateInstance(path/assemblyname, type) . Thanks!!!

  • Anonymous
    October 28, 2006
    Hi,The code is great. I am just trying out some project for my self. I have seen a lot of plugin based application so decided to build one.I have created similar code as yours but when the control comes to if condtionif((list<Type>)interface).contains(typeof(T)))It is not detecting the types as same. I don't know why. My spceifications areI have a public interface(IPlugin) under the same namespace(tstapp) in both the class library and the application.In the class library i have an class Class1 : IPlugin in the same namespace(tstapp)In application i call the getplugins and call one method in the plugin as specified.But the plugin never gets instantinated because of the reason specified aboveCan you find any defect !!!

  • Anonymous
    October 28, 2006
    Hi,  Thanks for the code. I found out the defect. check out -http://www.yoda.arachsys.com/csharp/plugin.htmlThe problem is that the IPlugin is loaded in the memory twice so that they are never evaluated as the same.The only possible solution for us is to create a class library of the Interface and add a reference to that in both the application and the class library(Plugin).It Works Great!!! Thanks for the code.

  • Anonymous
    April 29, 2008
    Thanks A ton man. U ROCK!!!!!. Although I was not making this kind of baby spy but i definately uprooted thousands of my hairs to make this Activator.CreateInstance work. Tried many overloads and changing dll from private to public and wasting several hours of my quality time untill i read this post. Its awesome.. thanks a lot. KR

  • Anonymous
    May 26, 2008
    var plugins = from string file in Directory.GetFiles(pluginDir, "*.dll")                          from type in Assembly.LoadFile(file).GetTypes()                          where type.IsClass && type.IsPublic                          from intfc in type.GetInterfaces()                          where intfc.Equals(typeof(IPlugin))                          select (IPlugin)Activator.CreateInstance(type); Same thing but using linq.

  • Anonymous
    September 30, 2008
    Just one problem!!! In case when the plugin assembly is dependent upon other libraries, which host app has no idea, how can you make sure that particular plugin is instantiated other than moving dependency dlls to either host prog path or GAC ????