The Simple Plugins Framework for .Net : Your favourite kind of Plugins in a sweet chocolaty AppDomain wrapper…

 

 

| Project Page | View Source | Releases | Change Log | Author’s Blog |

Simple Plugins is a .Net based plugins framework distributed under the GNU GPLv3 licence which can be used to create new pluggable applications as well as to make existing applications pluggable with a few lines of code. It’s a simple and easy to use .Net AppDomain based plugins solution which requires zero AppDomain knowledge on the part of the developer. The Simple Plugins library provides the following features and much more,

·         Easily make existing .Net applications pluggable with a few lines of code and zero app domain knowledge.

·         Write new pluggable .Net applications with a few lines of code and give plugin developers a simple API to work on that requires zero AppDomain knowledge.

·         Isolation of plugins in their own App Domains, Automatic Loading and Unloading of App Domains, Dependant Assembly Resolution, Shadow Copy Support and much more.

·         Prevent unhandled exceptions in plugins from crashing your app and prevent such exceptions from leaking AppDomains and causing unexpected behaviour in your pluggable application.

·         Define whether plugins can execute synchronously, asynchronously and specify whether plugins need to run exclusively.

·         Supports any type of plugin. GUI, Non-GUI etc. Supports plugins in the form of dynamic link libraries or executable files.

·         Keep track of loaded plugins. Be aware of each plugin's state and take actions (cancel plugin loading, customize app domain setup, etc) through numerous events exposed by the Plugin Loader.

·         Pass parameters to plugins and process their returned results seamlessly without having to worry about app domain logic.

·         Allow an app.config file per each plugin.

·         Not satisfied with the provided plugin or loader functionality? Easily write your own plugin class and define your own plugin loader by extending the provided classes.

The Simple Plugins library is contained within a single lightweight .Net assembly. Getting started with Simple Plugins is a piece of cake. An example pluggable application project is also included in the source code (downloadable from the link above) which thoroughly demonstrates the usage of the Simple Plugins library. The example uses a windows based application to demonstrate the usage. However this does not imply that Simple Plugins can only be used for Win Forms based applications.

First, the pluggable application.

1.     Open your pluggable application project in Visual Studio.

2.     Add a reference to the SimplePlugins.dll from your project.

3.     Add using/imports directives where you would like to initialize and use the plugin loader provided by the library like so,

using SimplePlugins;

using SimplePlugins.Exceptions;

 

4.     Initialize the PluginLoader object like so,

this.PluginLoader = new PluginLoader();

 

5.     You can specify additional paths where plugin dependencies will be located like so,

 

this.PluginLoader.AssemblyResolutionPaths.Add(Application.StartupPath);

 

6.     You can make the plugin loader use the .Net framework’s shadow copy option when loading assemblies like so, (You can further customize the cache path, etc by handling the PluginLoader.PluginLoading event)

 

this.PluginLoader.ShadowCopyEnabled = true;

 

7.     You can then hook up to the several events which enable the pluggable application to manage plugin behavior like so,

//This event fires when PluginLoaderBase.Probe is complete.

this.PluginLoader.ProbeComplete += new PluginLoaderBase.ProbeCompleteEventHandler(PluginLoader_ProbeComplete);

 

//This event fires before PluginLoaderBase.Load creates the AppDomain object which will be used to load the plugin from the plugin assembly.

this.PluginLoader.PluginLoading += new PluginLoaderBase.PluginLoadingEventHandler(PluginLoader_PluginLoading);

       

//This event fires when PluginLoaderBase.Load completes loading the plugin from the plugin assembly.

this.PluginLoader.PluginLoaded += new PluginLoaderBase.PluginLoadedEventHandler(PluginLoader_PluginLoaded);

 

//This event fires when PluginLoaderBase.Load completes the execution and unloading of the loaded plugin

this.PluginLoader.PluginUnloaded += new PluginLoaderBase.PluginUnloadedEventHandler(PluginLoader_PluginUnloaded);

 

8.     You can probe available plugins from a location in the hard disk as follows, The PluginLoader.Probe method scans the specified directory and subdirectories for valid plugin assemblies and returns a list.

PluginInfoList pluginInfoList = this.PluginLoader.Probe(“C:\plugins”, SearchOption.AllDirectories);

for (int i = 0; i < pluginInfoList.Count; i++)

lbPlugins.Items.Add(pluginInfoList[i]);

 

9.     Use the PluginLoader.Load method to execute any of the plugins returned by the PluginLoader.Probe method above. Parameters can also be passed to the plugin using the PluginParameters class. Any unhandled exceptions which may occur during the loading process is wrapped by the serializable PluginException class and rethrown to make sure no AppDomain leakages occur. Hence the try-catch block.

//Get the PluginInfo object selected by the user for execution.

PluginInfo template = (PluginInfo)lbPlugins.SelectedItem;

try

{

//Create a PluginParameters object and add any parameters to be passed to the plugin

PluginParameters args = new PluginParameters();

args.Add("Param1", "The quick brown fox jumped over the lazy dog");

args.Add("Param2", 100);

args.Add("Param3", 7.321);

 

//Tell the plugin loader to load and execute the plugin

this.PluginLoader.Load(template.FileName, args);

}

catch (PluginException ex)

{

Console.WriteException(ex);

}

 

10.  You may use the PluginLoader.NotifyUnload or PluginLoader.NotifyUnloadAll methods to notify a plugin that it needs to be unloaded.

11.  You can use the PluginLoader.PluginUnloaded event to check whether plugins executed successfully and their return values as follows.

void PluginLoader_PluginUnloaded(object sender, PluginLoaderBase.PluginUnloadedEventArgs e)

{

//Check if the plugin was unloaded due to an exception

//The PluginException class wraps any unhandled exception in a serializable wrapper.

//The PluginException class also exposes all InnerExceptions of the actual exception through PluginException.InnerException as well

//This makes sure that unhandled non-serializable exceptions do not cause unexpected AppDomain leaks in the main application.

if (e.Results.UnhandledException != null)

Console.WriteException("The plugin '" + e.Info.ToString() + "' was unloaded due to unhandled exception: " + e.Results.UnhandledException.ToString());

else if (e.Results.ExecutionDenied)

{

//Plugins can be denied execution by the PluginLoader object according to the specified value in PluginBase.ExecutionMode property.

//This checks which execution modes prevented the plugin from being executed.

if (e.Info.ExecutionMode == PluginBase.ExecutionModes.AsynchronousExclusive || e.Info.ExecutionMode == PluginBase.ExecutionModes.SynchronousExclusive)

              Console.WriteInfo("The plugin '" + e.Info.ToString() + "' was unloaded since it is intended to be executed exclusively. Please make sure no other plugin is executing and try again.");

else if (e.Info.ExecutionMode == PluginBase.ExecutionModes.AsynchronousSingleton || e.Info.ExecutionMode == PluginBase.ExecutionModes.SynchronousSingleton)

Console.WriteInfo("The plugin '" + e.Info.ToString() + "' was unloaded since only one instance of it is intended to be executed at a time. Please make sure no other instance of the same plugin is executing and try again.");

else

Console.WriteInfo("The plugin '" + e.Info.ToString() + "' was unloaded.");

}

else

{

//Each plugin may return results in the form of a PluginParameters object exposed by the PluginLoaderBase.PluginUnloadedEventArgs.Results property

//This object can be checked for values returned by the plugin like so,

if (e.Results.Contains("ReturnValue"))

Console.WriteInfo("The plugin '" + e.Info.ToString() + "' was unloaded successfully with the return value: " + Convert.ToString(e.Results.Get("ReturnValue")));

else

Console.WriteInfo("The plugin '" + e.Info.ToString() + "' was unloaded successfully");

}

}

 

That’s it! You have got yourself a pluggable application. Now you can write a plugin by following the steps below.

1.     Open or create a project for the plugin in Visual Studio. This can be an executable type project or a class library project.

2.     Add a reference to the SimplePlugins.dll.

3.     Add a class under any name which will represent the plugin. Derive this class from the PluginBase abstract class provided by the Simple Plugins library and override its methods appropriately. Place your main processing code inside the Main method. The Main method is called by the PluginLoader.Load method after the plugin is successfully loaded.

using SimplePlugins;

using SimplePlugins.Exceptions;

 

namespace SamplePluggableApp.SamplePlugin

{

    public class Plugin : PluginBase

    {

        public Plugin()

            : base()

        {

           

        }

 

        internal string Param1 { get; private set; }

        internal int Param2 { get; private set; }

        internal double Param3 { get; private set; }

 

        public override string FriendlyName

        {

            //The friendly name of the plugin. This will be used by default in PluginInfo.ToString()

            get { return "Sample Plugin"; }

        }

 

        public override ExecutionModes ExecutionMode

        {

            get { return ExecutionModes.AsynchronousMultiInstance; } //Tells Loader to allow loading of multiple instances asynchronously in seperate threads

            //ExecutionModes.AsynchronousExclusive - PluginLoaderBase object will execute plugin in a seperate thread only if no other plugins are running

            //ExecutionModes.AsynchronousMultiInstance - PluginLoaderBase object will execute plugin in a seperate thread and will allow multiple instances of the plugin to be executed simultaneously

            //ExecutionModes.AsynchronousSingleton - PluginLoaderBase object will execute plugin in a seperate thread and will allow only one instance of the plugin to be running at a time

            //ExecutionModes.SynchronousExclusive - PluginLoaderBase object will execute plugin in the same thread as the pluggable app only if no other plugins are running

            //ExecutionModes.SynchronousMultiInstance - PluginLoaderBase object will execute plugin in the same thread as the pluggable app and will allow multiple instances of the plugin to be executed simultaneously

            //ExecutionModes.SynchronousSingleton - PluginLoaderBase object will execute plugin in the same thread as the pluggable app and will allow only one instance of the plugin to be running at a time

        }

 

        public override PluginParameters Main(PluginParameters args)

        {

            //Assigns parameters passed from the pluggable app to public members

            this.Param1 = (string)args.Get("Param1");

            this.Param2 = (int)args.Get("Param2");

            this.Param3 = (double)args.Get("Param3");

           

            MainView pluginForm = new MainView();

            pluginForm.Show(); //show the main plugin window

 

            Application.Run(pluginForm); //This starts a new message loop for the plugin window. pluginForm.ShowDialog() may also be used instead.

            //Specifying ExecutionMode as Synchronous* and calling pluginForm.ShowDialog() will show the form in dialog mode and block the Pluggable App.

 

            args.Add("ReturnValue", "Success"); // Demonstrates how serializeable/primitive types can be passed back to the pluggable app

            //returned values will be available in the pluggable app via PluginLoaderBase.PluginUnloaded event

 

            return args;

        }

 

        public override void OnUnloadNotification()

        {

            //This method is called by PluginLoaderBase.NotifyUnload or PluginLoaderBase.NotifyUnloadAll to notify the plugin that it is about to be unloaded.

            //You can write code which will stop plugin process and do cleanup here

            Application.Exit();

        }

    }

}

 

 

That’s all there is to it. Place the output of your plugin project in the plugins folder path and let Simple Plugins do everything else for you. If the functionality provided by the standard PluginLoader and PluginBase classes are not enough, you can extend those classes to meet your own needs. You may refer the code on how the default PluginLoader class is implemented to get an idea about writing a custom loader.