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.