Example plugin system
I came up with such a task as how to develop a system of plug-ins (add-ins) for the program. Digging a little AddIn which were offered since .NET 3.5, I realized that they do not suit me. Firstly, this is not a convenient directory location, a lot of extra. Secondly, it is not possible to infiltrate into any part of the process and do it your own way, i.e. low extensibility.
And he decided to invent a bicycle ...
I based on isolation from the main application and the possibility of a sandbox. All this is not difficult to do by creating a new AppDomain and configuring it on InternetZone. But during development, I ran into a problem when loading the assembly with the plugin into the domain. The problem was a communications error. For loading I use AppDomain.Load (). As it turned out later, this method does not suit me. Therefore, I did not have to make a large crutch, which I will talk about.
Let's start by creating a new project and a console application (I named it PluginHost) and add another class library project (let's call it PluginFramework). Add the class to the library:
And one more class, without which it was difficult to implement the logic I needed:
The base frame is ready. Not a big digression. What exactly did I want to achieve:
1. Once again, isolation from the main application
2. At any time, loading and unloading the plug-in. If, for example, a plugin crashes, we simply unload it and reload
it. What’s in our Main:
First, I load the PluginFramework assembly in reflection-only mode and get the Type of our base class for all plugins:
Then search in all available folders
And again, I load the assembly in reflection-only mode, but with the plugin and look for a class that inherits from the Plugin base class. So that we can load the assembly with the plug-in here and compare it with the base type, we had to load it at the beginning of PluginFramework only for reflection.
Now you can start creating a new domain, load everything you need into it and run our plugin to execute
That's all!
And write a test plugin to test
The directory structure is as follows Link to sources PluginSystem.zip P.S. I load the assemblies for reflection, because if we simply upload the assembly to a new domain, we will not be able to go through it from the child domain and find the class we need. The CLR will try to load this assembly into a child domain
And he decided to invent a bicycle ...
I based on isolation from the main application and the possibility of a sandbox. All this is not difficult to do by creating a new AppDomain and configuring it on InternetZone. But during development, I ran into a problem when loading the assembly with the plugin into the domain. The problem was a communications error. For loading I use AppDomain.Load (). As it turned out later, this method does not suit me. Therefore, I did not have to make a large crutch, which I will talk about.
Let's start by creating a new project and a console application (I named it PluginHost) and add another class library project (let's call it PluginFramework). Add the class to the library:
- public abstract class Plugin: MarshalByRefObject
- {
- public virtual void Initialize()
- {
- }
- }
* This source code was highlighted with Source Code Highlighter.
And one more class, without which it was difficult to implement the logic I needed:
- public class AssemblyHelper: MarshalByRefObject
- {
- private AppDomain _currentDomain;
- public AssemblyHelper()
- {
- _currentDomain = AppDomain.CurrentDomain;
- _currentDomain.AssemblyResolve += new ResolveEventHandler(_currentDomain_AssemblyResolve);
- }
-
- Assembly _currentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
- {
- string[] nameSplit = e.Name.Split(',');
- string path = Path.Combine(SearchFolder, nameSplit[0] + ".dll");
- Assembly loadedAssembly;
- try
- {
- loadedAssembly = Assembly.LoadFile(path);
- }
- catch (Exception exc)
- {
- Exception exp = exc;
- throw;
- }
- if (loadedAssembly != null)
- {
- return loadedAssembly;
- }
- else
- {
- return null;
- }
- }
-
- public string SearchFolder { get; set; }
- }
* This source code was highlighted with Source Code Highlighter.
The base frame is ready. Not a big digression. What exactly did I want to achieve:
1. Once again, isolation from the main application
2. At any time, loading and unloading the plug-in. If, for example, a plugin crashes, we simply unload it and reload
it. What’s in our Main:
First, I load the PluginFramework assembly in reflection-only mode and get the Type of our base class for all plugins:
- Assembly frameworkReflect = Assembly.ReflectionOnlyLoadFrom(Path.Combine(Environment.CurrentDirectory, "PluginFramework.dll"));
- Type basePluginType = frameworkReflect.GetType("MyPluginSample.Framework.Plugin");
* This source code was highlighted with Source Code Highlighter.
Then search in all available folders
- string pathPlugins = Path.Combine(Environment.CurrentDirectory, "Plugins");
- DirectoryInfo directoryPlugins = new DirectoryInfo(pathPlugins);
- foreach (var dir in directoryPlugins.GetDirectories())
- {
- FileInfo dllFile = dir.GetFiles(dir.Name + ".dll").First();
* This source code was highlighted with Source Code Highlighter.
And again, I load the assembly in reflection-only mode, but with the plugin and look for a class that inherits from the Plugin base class. So that we can load the assembly with the plug-in here and compare it with the base type, we had to load it at the beginning of PluginFramework only for reflection.
- Assembly reflectionAsm = Assembly.ReflectionOnlyLoadFrom(dllFile.FullName);
- Type typePlugin = reflectionAsm.GetTypes().First(t => t.IsSubclassOf(basePluginType));
* This source code was highlighted with Source Code Highlighter.
Now you can start creating a new domain, load everything you need into it and run our plugin to execute
- AppDomain pluginDomain = AppDomain.CreateDomain(dir.Name + " plugin");
- AssemblyHelper helper = (AssemblyHelper)pluginDomain.CreateInstanceAndUnwrap("PluginFramework", "MyPluginSample.Framework.AssemblyHelper");
- helper.SearchFolder = dir.FullName;
- Plugin plugin = (Plugin)pluginDomain.CreateInstanceAndUnwrap(reflectionAsm.FullName, typePlugin.FullName);
- plugin.Initialize();
* This source code was highlighted with Source Code Highlighter.
That's all!
And write a test plugin to test
- public class MyPlugin1: Plugin
- {
- public override void Initialize()
- {
- Console.WriteLine("Executing in Plugin 1. Domain Id: {0}", AppDomain.CurrentDomain.Id);
- }
- }
* This source code was highlighted with Source Code Highlighter.
The directory structure is as follows Link to sources PluginSystem.zip P.S. I load the assemblies for reflection, because if we simply upload the assembly to a new domain, we will not be able to go through it from the child domain and find the class we need. The CLR will try to load this assembly into a child domain