Click here to Skip to main content
Click here to Skip to main content

Plugin Architecture using C#

By , 3 Aug 2003
 

Sample Image - Plugins.jpg

Introduction

This article demonstrates to you how to incorporate a single module, as a plugin for another application or use it as a standalone application. The article will demonstrate how a minimal change is required to obtain the above result.

Background

The basic idea for this article has been adopted from the net, I don't remember the original author, but I found the code as a console application, while what I was searching was a method to use one of my windows application module in another application and run it standalone as well with as minimal changes a possible. A little changes to the console application, and this article is what the final result looks like.

Using the code

The key to a plugin architecture is the implementation of a minimal set of methods by the plugins. These methods are used by the main (incorporating) application to find and recognize the plugins. This minimal set of method is achieved by making an interface, which would declare the methods, that are to be implemented by the plugins. This interface has been defined as follows:

public interface IPlugin
{
    string Name{get;set;}
    IPluginHost Host{get;set;}
    void Show();
}

The interface define a few methods. Name is used to get the name of the plugin to be shown on the main application in the menu. IPluginHost is used to let the plugin know who is hosting the plugin. Show will show the main form of the plugin application.

We now make our first plugin in form of a windows application like this.

using System;
using PlugIn;

namespace Dynamic
{
    class PlugIn : IPlugin
    {
        private string m_strName;
        private IPluginHost m_Host;
        
        public PlugIn()
        {
            m_strName = "Dynamic"; 
        }
        
        public string Name
        {
            get{return m_strName;}
            set{m_strName=value;}
        }
        
        public void Show()
        { 
            Main1 mn = new Main1();
            mn.ShowDialog();
        }

        public IPluginHost Host
        {
            get{return m_Host;}
            set
            {
                m_Host=value;
                m_Host.Register(this);
            }
        }
    }
}

public class Main1 : System.Windows.Forms.Form 
{ 
    //Your original code goes here...

    [STAThread]
    static void Main() 
    {
        Application.Run(new Main1());
    }
}

The above code declare the a plugin class so that the parent application could recognize it as a plugin. The application also declare a form class, which is the startup form.

Now compile the plugin as an executable file. Run it, it should run as a normal standalone exe. Copy the exe into the main applications executable directory, and rename it as a DLL instead of a exe.

Now we move to our main application:

for that we have to interface, which is implemented by our application so that it registers with the plugins.

    public interface IPluginHost
    {
        bool Register(IPlugin ipi);
    }

The main application is implemented as windows form.

public class Form1 : System.Windows.Forms.Form, IPluginHost 
{
    private System.Windows.Forms.MainMenu mainMenu1;
    private System.Windows.Forms.MenuItem menuItem1; private IPlugin[] ipi;

    private void Form1_Load(object sender, System.EventArgs e)
    {
        string path = Application.StartupPath;
        string[] pluginFiles = Directory.GetFiles(path, "*.DLL");
        ipi = new IPlugin[pluginFiles.Length];

        for(int i= 0; i<pluginFiles.Length; i++)
        {
            string args = pluginFiles[i].Substring(
                pluginFiles[i].LastIndexOf("\\")+1,
                pluginFiles[i].IndexOf(".DLL")-
                pluginFiles[i].LastIndexOf("\\")-1);

            Type ObjType = null;
            try
            {
                // load it
                Assembly ass = null;
                ass = Assembly.Load(args);
                if (ass != null)
                {
                    ObjType = ass.GetType(args+".PlugIn");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            try
            {
                // OK Lets create the object as we have the Report Type
                if (ObjType != null)
                {
                    ipi[i] = (IPlugin)Activator.CreateInstance(ObjType);
                    ipi[i].Host = this;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

}

In OnLoad(), we check for all the DLLs present in the application directory, and scan if any implement the plugin interface. If it does we store it and register the plugin with us. This plugin in turn calls the register method on the main application. The register method add the plugin name with out menu.

        public bool Register(IPlugin ipi)
        {
            MenuItem mn = new MenuItem(ipi.Name,new EventHandler(NewLoad));

            Console.WriteLine("Registered: " + ipi.Name);
            menuItem1.MenuItems.Add(mn);
            return true;
        }        

The next time the plugin menu is click, we check for the respective plugin name and call the appropriate plugin.

private void NewLoad(object sender, System.EventArgs e) {
    
    MenuItem mn = (MenuItem)sender; for(int

    i=0; i < ipi.Length; i++)
    {
        string strType = mn.Text;
        if(ipi[i]!=null)
        {
            if(ipi[i].Name==strType)
            {
                ipi[i].Show();
                break;
            }
        }
    }            
}    
        

Now compile and run the application, with the plugin DLL in the application path, the plugin name would show in the menu. Click the menu and the plugin pops up. Hope you find the code useful.

History

  • This is the initial release 1.0.0R.

License

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

About the Author

shokisingh
Team Leader
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5membermike_dcsku25 Sep '12 - 18:37 
it is good
GeneralWhy use IPluginHostmembernulkoud16 Jun '09 - 1:41 
Hi all,
 
I was wondering why I should use the IPluginHost interface. I've got the code now working without and was wondering why I should implement the host part. Can somebody explain that to me?
 
thanks!
GeneralRe: Why use IPluginHostmembersameh_serag15 Oct '09 - 2:44 
If you decide to access the host from the plugin (for example to add some controls to its main form) then the plugin may need to know much about its host.
GeneralCastmemberassismvla19 Apr '09 - 10:13 
ipi[i] = (IPlugin)Activator.CreateInstance(ObjType);
 
"Unable to cast object of type 'PluginTest.PlugIn' to type IPlugin"
 
??
GeneralRe: CastmemberManadar14 May '09 - 3:25 
Make sure your PlugIn class implements interface IPlugin:
 
public class PlugIn : IPlugin
 
Hope it helps.
GeneralConvert to VB.netmemberMichalss28 Mar '09 - 22:49 
Hello im trying to convert it to vb.net over 3 days but im not sucess can anyone help please??? thx
GeneralProof read proof read proof read!!!!!memberhavyck5 Apr '07 - 10:08 
I still say these articles are HORRIBLY proof read. Dead | X| It makes it much harder to follow a tutorial when it's written by someone who is clearly not skilled in writing English. It seems to me that the editors of the site need to do a BETTER job of proof reading these articles and not settle for "good enough."Mad | :mad:
GeneralRe: Proof read proof read proof read!!!!!memberjustapgmr14 Jan '09 - 5:23 
Who cares? Figure it out own your own then.
GeneralRe: Proof read proof read proof read!!!!!memberwibblewoo28 Jan '09 - 1:22 
Professionals care.
GeneralRe: Proof read proof read proof read!!!!!memberjt_2312 Jul '09 - 5:04 
Agreed.
GeneralA little bit code optimizationmemberAlexander M. Batishchev11 Apr '06 - 0:13 
Hi! Thanks for your code - it's great!
 
And I have little suggestion: instead of
 
string args = pluginFiles[i].Substring(
pluginFiles[i].LastIndexOf("\\") + 1,
pluginFiles[i].IndexOf(".dll") -
pluginFiles[i].LastIndexOf("\\") - 1);

 
it's better to use:
 
Path.GetFileNameWithoutExtension(pluginFiles[i]);
 
Am I right? Smile | :)
GeneralRe: A little bit code optimizationmemberAlexander M. Batishchev11 Apr '06 - 12:47 
and another one: instead of
 
Type ObjType = null;
Assembly ass = null;
ass = Assembly.Load(args);
if (ass != null)
{
  ObjType = ass.GetType(args+".PlugIn"); //As I can understand, it has to use SomeNamespace.SomeInterface
}
if (ObjType != null)
{
  ipi[i] = (IPlugin)Activator.CreateInstance(ObjType);
  ipi[i].Host = this;
}

 
use
 
Assembly asm;
asm = Assembly.LoadFile(imFile);
Type[] types = asm.GetTypes();
 
if (asm != null)
{
  foreach (Type t in types)
  {
//Works if there are only classes with type of IPlugin (such PlugIn is) in selected plugin.
//Else additional if is required:
//  if (t().ToString() == "Dynamic.PlugIn")
//  {
        imList[i] = (IModule)Activator.CreateInstance(t);
        imList[i].Host = this;
//  }
  }
}

GeneralRe: A little bit code optimizationmemberDavid30019 Dec '06 - 7:48 
Just another thing to add:
You can use IsAssignableFrom(..) to check whether a type implements your interface. thus you are not limited to naming restrictions/conventions.
 

 
Assembly asm;
asm = Assembly.LoadFile(imFile);
Type[] types = asm.GetTypes();
 
if (asm != null)
{
foreach (Type t in types)
{
// CHECK IF CURRENT TYPE IMPLEMENTS INTERFACE IPlugin
if (typeof(IPlugin).IsAssignableFrom(t))
{
imList[i] = (IPlugin)Activator.CreateInstance(t);
imList[i].Host = this;
}
}
}
 

GeneralRe: A little bit code optimizationmemberAlexander M. Batishchev3 Apr '07 - 4:21 
Thx! Smile | :)
GeneralRe: A little bit code optimizationmemberHenk Burgstra4 Aug '07 - 15:21 
Minor correction:
Type[] types = asm.GetTypes();
should be inside the if-clause.
GeneralRe: A little bit code optimizationmemberBZZR14 Nov '07 - 19:49 
Cool. Much more bullet proof. Nice contribution. Smile | :)
GeneralRe: A little bit code optimizationmemberMember 20530069 Sep '09 - 0:30 
Nice bit of code there.
 
Just one thing I discovered on my quest to break this code - if the interface IPlugin changes (naughty!) and the plugins are not updated then the line:
 
Type[] types = asm.GetTypes();
 
can throw an exception. It is worth putting this in a try-catch block so the other plugins can be loaded.
Questionother directory than app path??memberBadscher14 Feb '06 - 23:42 
Hi,
 
I would like to put the dlls in another directory! Its no problem to find them, but I get an exception by calling the function getType()!
 
"File or assembly name xyz, or one of its dependencies, was not found."
 
What to do!?
 


AnswerRe: other directory than app path??memberAlexander M. Batishchev12 Apr '06 - 1:15 
Hi! See my code below
You need to use LoadFile() first of all, and geting type would be successful
GeneralReflection in VB2005memberhrgy846 Jan '06 - 7:53 
It's a very good and useful article, and I adapted it for VB.NET in VS2005.
 
If others would use it, I share my knowledge.
 
I don't understand the problem correctly, but the VB can't use this code:
ObjType = ass.GetType(args+".PlugIn")
Confused | :confused:
I tested the correct version. Here:

Dim ObjType As Type = Nothing
 
If ass IsNot Nothing And ass.FullName.StartsWith("PEFilter.") Then
assname = plFile.Name.Substring(0, plFile.Name.LastIndexOf(".")).Replace("PEFilter.", "")
For Each ObjType In ass.GetTypes
If ObjType.Name = assname Then Exit For
Next

 
Comments:
 
I get the name of assembly from the filename. And I start a search, and I stop it when i found the correct ObjType. It's dont have a very different from the directly request but it works correctly.
 
I hope, I can help for people, who would use the reflection.
 
And i very-very thanks this article for Shoki! It was a tutorial for me to use a reflection.
 
PS: Sorry, i dont's speak english very well... Frown | :(
GeneralSecurity concernsmemberBiju P K17 May '05 - 0:13 
Smile | :) Hi ,
 
The code is very simple and nice, one thing what i feel is secuirity issue. if we are developing a full GUI based app with this model, then anybody can hook any plugins(which supports the interface) to the main app and try to hack the app. so one suggestion is always include a plugin authentication mechanism also in the main app (using digital signature or some other thing).
 
regards,
Biju
GeneralRe: Security concernsmemberValer BOCAN26 Sep '05 - 2:44 
You may very well use CAS and demand StrongNameIdentityPermission. You have to sign the assemblies though.
 
Delta Forth .NET (www.dataman.ro)
World's first Forth compiler for the .NET platform
GeneralRe: Security concernsmemberMrOzark16 Oct '05 - 19:07 
Are you aware of any new stuff - like from 2005 in the area of security and plugins?
GeneralRe: Security concernsmemberJerod Edward Moemeka8 Jun '06 - 7:12 
Not just that, a malicious coder can create a plugin that accesses other resources on the target computer. A good plugin architecture MUST restrict what resources an assembly has access to using CAS. In fact I dare say it should pervent ALL access to any resources directly from the plugin assembly. Anything the assembly needs should be comming from the main app through events or callbacks so as to control malicious code from running. What do you think.
 
Jerod Edward Moemeka
Genesys Corporation
GeneralChild Formsmemberirungu4 May '05 - 0:22 
I like Your plugin architecture because it is simple. One Question.
How do I ensure that the main forms of the plugins are child forms of the host's mainform

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 4 Aug 2003
Article Copyright 2003 by shokisingh
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid