⬆️ ⬇️

Plugin system example

I came up with the task of developing a system of plug-ins (add-ins) to the program. A little digging AddIn which have been proposed since. NET 3.5, I realized that they do not suit me. Firstly, this is not a convenient location of directories, a lot of unnecessary. Secondly, it is not possible to infiltrate into any part of the process and do it your way, i.e. weak extensibility.

And I decided to invent a bicycle ...



The basis I put isolation from the main application and the possibility of a sandbox. This is all not difficult to do by creating a new AppDomain and configuring it on InternetZone. But during development I encountered a problem when loading an assembly with a plugin into a domain. The problem was a link error. For download, I use AppDomain.Load (). As it turned out later, this method does not suit me. In this, I had to make a small crutch, which I will tell you about.



Start by creating a new project and a console application (I called it PluginHost) and add another project class library (let's call it PluginFramework). Add a class to the library:

public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
  1. public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
  2. public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
  3. public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
  4. public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
  5. public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
  6. public abstract class Plugin: MarshalByRefObject { public virtual void Initialize() { } } * This source code was highlighted with Source Code Highlighter .
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:

  1. public class AssemblyHelper: MarshalByRefObject
  2. {
  3. private AppDomain _currentDomain;
  4. public AssemblyHelper ()
  5. {
  6. _currentDomain = AppDomain.CurrentDomain;
  7. _currentDomain.AssemblyResolve + = new ResolveEventHandler (_currentDomain_AssemblyResolve);
  8. }
  9. Assembly _currentDomain_AssemblyResolve ( object sender, ResolveEventArgs e)
  10. {
  11. string [] nameSplit = e.Name.Split ( ',' );
  12. string path = Path.Combine (SearchFolder, nameSplit [0] + ".dll" );
  13. Assembly loadedAssembly;
  14. try
  15. {
  16. loadedAssembly = Assembly .LoadFile (path);
  17. }
  18. catch (Exception exc)
  19. {
  20. Exception exp = exc;
  21. throw ;
  22. }
  23. if (loadedAssembly! = null )
  24. {
  25. return loadedAssembly;
  26. }
  27. else
  28. {
  29. return null ;
  30. }
  31. }
  32. public string SearchFolder { get ; set ; }
  33. }
* This source code was highlighted with Source Code Highlighter .


Base frame is ready. Not a big digression what I actually wanted to achieve:

1. Once again isolation from the main application

2. At any time loading and unloading the plugin. If for example the plugin has fallen, we will simply unload it and reload it.

')

What we have in Main:

First, I load the PluginFramework assembly in reflection-only mode and get the Type of our base class for all plugins:

  1. Assembly frameworkReflect = Assembly .ReflectionOnlyLoadFrom (Path.Combine (Environment.CurrentDirectory, "PluginFramework.dll" ));
  2. Type basePluginType = frameworkReflect.GetType ( "MyPluginSample.Framework.Plugin" );
* This source code was highlighted with Source Code Highlighter .


Then search all available folders.

  1. string pathPlugins = Path.Combine (Environment.CurrentDirectory, "Plugins" );
  2. DirectoryInfo directoryPlugins = new DirectoryInfo (pathPlugins);
  3. foreach ( var dir in directoryPlugins.GetDirectories ())
  4. {
  5. FileInfo dllFile = dir.GetFiles (dir.Name + ".dll" ) .First ();
* This source code was highlighted with Source Code Highlighter .


And again I load the assembly in the mode only for reflection, but already with the plugin and am looking for a class that inherits from the base class Plugin. So that we could load the assembly with the plugin here and compare it with the base type we had to load PluginFramework at the beginning too, just for reflection.

  1. Assembly reflectionAsm = Assembly .ReflectionOnlyLoadFrom (dllFile.FullName);
  2. 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 launch our plugin for execution

  1. AppDomain pluginDomain = AppDomain.CreateDomain (dir.Name + "plugin" );
  2. AssemblyHelper helper = (AssemblyHelper) pluginDomain.CreateInstanceAndUnwrap ( "PluginFramework" , "MyPluginSample.Framework.AssemblyHelper" );
  3. helper.SearchFolder = dir.FullName;
  4. Plugin plugin = (Plugin) pluginDomain.CreateInstanceAndUnwrap (reflectionAsm.FullName, typePlugin.FullName);
  5. plugin.Initialize ();
* This source code was highlighted with Source Code Highlighter .


That's all!

And write a test plugin to check

  1. public class MyPlugin1: Plugin
  2. {
  3. public override void Initialize ()
  4. {
  5. Console .WriteLine ( "Executing in Plugin 1. Domain Id: {0}" , AppDomain.CurrentDomain.Id);
  6. }
  7. }
* This source code was highlighted with Source Code Highlighter .


The directory structure is as follows





Link to source PluginSystem.zip



PS I load the assemblies for reflection, because if we simply load the assembly into 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 attempt to load this build into the child domain.

Source: https://habr.com/ru/post/101547/



All Articles