Pages

Tuesday, October 30, 2012

Extensibility of the desktop applications

Application extensibility question arises in almost every project. For web-applications, it can be, for example, horizontal and vertical scalability, and in terms of desktop applications, it may be a new version or the use of plug-ins.

Releasing of a new version is simple. When there are enough extra stuff, you just build your application, assign a new version number and write release notes. Deploy and enjoy!

Plugins are much more complex. First of all, you have to foresee what features will be able to be extensible through plugins. The main application must be able to register a necessary plugins and run them at the right time. Cooperation of two plugins is also regulated by the main application.

From .Net 4.0 the task of developing and supporting plugins become much easier, as there was presented MEF - Managed Extensibility Framework. I will describe the work with it little later, and now I would like to concentrate on the simplest example of a plugin development in .Net 2.0

In .Net 2.0 it can be done throught reflection. For simplicity it is better to implement some common inteface in your plugins, and use that interface in the main application. So, your programm should know where to look for plugins and how to run them.
Here is simple example: application that do nothing except for running plugins, user can run every plugin with buttons from main application.

PluginHost - simple win forms application. It does not have any control and there is only 2 handlers - Form_Load and Button_Click
private void MainForm_Load(object sender, EventArgs e)
{
    //Search for dlls
    string pathToAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
    pathToAssembly = Path.GetDirectoryName(pathToAssembly) + "\\plugins";

    int plCount = 0; //number of correct plugins
    foreach (var file in Directory.GetFiles(pathToAssembly, "*.dll", SearchOption.TopDirectoryOnly))
    {
        var pluginModule = System.Reflection.Assembly.LoadFrom(file); //loading assembly
        var types = pluginModule.GetTypes(); //getting all the types inside it
        foreach (var type in types)
        if (type.GetInterface("IMyPlugin") != null) //if we found correct type
        { //then lets add button for this plugin
            Button newButton = new Button()
            {
                Size = new System.Drawing.Size(100, 25),
                Location = new Point(10, 10 + 25 * plCount),
                Text = Path.GetFileName(file),
                Tag = file
            };
            plCount++;
            newButton.Click += pluginButton_Click; //handler for plugin execution
            this.Controls.Add(newButton);
        }
    }
}

void pluginButton_Click(object sender, EventArgs e)
{
    //once again - load assembly
    var pluginModule = System.Reflection.Assembly.LoadFrom((sender as Button).Tag.ToString());
    var types = pluginModule.GetTypes();
    foreach (var type in types) //find interface
    if (type.GetInterface("IMyPlugin") != null)
    {
        //create instance of that class
        var pluginEntry = Activator.CreateInstance(type, null);
        //and run entry method
        System.Reflection.MethodInfo methodInfo = type.GetMethod("DoSomething");
        methodInfo.Invoke(pluginEntry, null);
    }
}
As you can see I am assuming that my plugins implement IMyPlugin interface, and there is DoSomething() method (declared in that interface). All plugins must be located in plugins directory near main executable.

MyPlugin is a dll (class library). It has interface declaration
public interface IMyPlugin
{
    void DoSomething();
}
And another form with one button that joyfully shouts of her working capacity
public partial class AdditionalForm : Form, IMyPlugin
{
    public AdditionalForm()
    {
        InitializeComponent();
    }

    private void btnShow_Click(object sender, EventArgs e)
    {
        MessageBox.Show("I am plugin!");
    }

    public void DoSomething()
    {
        this.ShowDialog();
    }
}