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 box

foreach (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 (29)

  1. Name Required says:

    " 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.

  2. 🙂 I do have a 11 month old daughter

  3. Ruediger says:

    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

  4. 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

  5. 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.

  6. Sometime back I had posted about writing applications that can load plugins using late binding. Users…

  7. JSheble says:

    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?

  8. abhinaba says:

    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);

  9. Joe says:

    On this line:

    if (((IList)interfaces).Contains(typeof(T)))

    I get the following error:

    Error 1 Cannot convert type ‘System.Type[]’ to ‘System.Collections.Generic.IList<T>’ C:NET 2.0 ProjectsPluginTestPluginHostTestForm1.cs 37 30 PluginHostTest

    I see that someone elese had this same problem and changed it to:

    if (((IList<T>)interfaces).Contains(typeof(T)))

    But I still get the error after doing this as well…

  10. Joe says:

    never mind, I got it figured out…

    Thanx for this article!!

  11. Joe says:

    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!

  12. abhinaba says:

    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…

  13. Jaydeep says:

    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

  14. 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

  15. abhinaba says:

    In the plugin assembly I implement the interface as in

    public class MyMotionDetector : IMotionDetector

    {

    // implement the methods of IMotionDetector

    }

    The actual application picks up this assembly get all types with this interface and creates as object of them. So the instance of MyMotionDetector is created by the following line in the GetPlugin method

    object obj = Activator.CreateInstance(type);

  16. taamneh says:

    i thank you to your effort in provide the sociaty enviroment with good tools and experiance

     

    taamneh

  17. c# user says:

    Great. Good, very useful piece of code. Thanks buddy.

  18. Gangesh says:

    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.

  19. Great job Abhinaba.  I found this really useful.

  20. abhinaba says:

    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…

  21. Gangesh says:

    Abhinaba, Thanks alot for that code piece and idea (setting aliases). That’s useful. However actually I have to have only one assembly loaded up at anytime (due to memory constrains on the server).

    I have tried this at design time (compilation time) by swapping between the dll references, and could have two different Applications. But when needed to change to the other one have to recompile with the reference changed to the other dll. Involves going into the studio again. So thought of doing this at run time and refreshing the server to get the other dll.

    I tried Activator.CreateInstance(path/assemblyname, type) without doing an Assembly.LoadFile(file). But was getting a runtime error message saying "Could not load file or assembly ‘pathassemblyname’ or one of its dependencies. The given assembly name or codebase was invalid. "

    I don’t know what I am missing!!!

    Thanks for your advice.

  22. abhinaba says:

    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….

  23. Gangesh says:

    May be some permission issue since I am running "Activator.CreateInstance" from the web server ?

  24. Gangesh says:

    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!!!

  25. Arun says:

    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 condtion

    if((list<Type>)interface).contains(typeof(T)))

    It is not detecting the types as same. I don’t know why. My spceifications are

    I 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 above

    Can you find any defect !!!

  26. Arun says:

    Hi,

      Thanks for the code. I found out the defect. check out –

    http://www.yoda.arachsys.com/csharp/plugin.html

    The 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.

  27. Sunny says:

    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

  28. Itamar says:

    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.

  29. Amorphic says:

    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 ????