How can I communicate between plugins?

I have a plugin system in which I use MarshalByRefObject to create isolated domains for each plugin, so users can reload their new versions as they see fit without shutting down the main application.

Now I need to allow the plugin to see which plugins are currently running, and possibly start / stop a particular plugin.

I know how to issue commands from the shell in the code below, for example:

 using System; using System.Linq; using System.Reflection; using System.Security.Permissions; namespace Wrapper { public class RemoteLoader : MarshalByRefObject { private Assembly _pluginAassembly; private object _instance; private string _name; public RemoteLoader(string assemblyName) { _name = assemblyName; if (_pluginAassembly == null) { _pluginAassembly = AppDomain.CurrentDomain.Load(assemblyName); } // Required to identify the types when obfuscated Type[] types; try { types = _pluginAassembly.GetTypes(); } catch (ReflectionTypeLoadException e) { types = e.Types.Where(t => t != null).ToArray(); } var type = types.FirstOrDefault(type => type.GetInterface("IPlugin") != null); if (type != null && _instance == null) { _instance = Activator.CreateInstance(type, null, null); } } public void Start() { if (_instance == null) { return; } ((IPlugin)_instance).OnStart(); } public void Stop() { if (_instance == null) { return; } ((IPlugin)_instance).OnStop(close); } } } 

So, I could, for example:

 var domain = AppDomain.CreateDomain(Name, null, AppSetup); var assemblyPath = Assembly.GetExecutingAssembly().Location; var loader = (RemoteLoader)Domain.CreateInstanceFromAndUnwrap(assemblyPath, typeof(RemoteLoader).FullName); loader.Start(); 

Of course, the above is just a renewed pattern ...

Then on my wrapper I have methods like:

 bool Start(string name); bool Stop(string name); 

Basically, this is a shell for starting the Start / Stop of a particular plugin from the list and the list for tracking the launch of plugins:

 List<Plugin> Plugins 

A plugin is just a class containing Domain , RemoteLoader , etc.

I do not understand how to achieve this from inside the plugin. Be able to:

  • View a list of running plugins
  • Start or stop for a specific plugin

Or, if this is possible even with MarshalByRefObject , provided that the plugins are isolated or will I need to open a different communication route to achieve this?

For generosity, I am looking for a working verifiable example of the above ...

+5
source share
2 answers

First we define a couple of interfaces:

 // this is your host public interface IHostController { // names of all loaded plugins string[] Plugins { get; } void StartPlugin(string name); void StopPlugin(string name); } public interface IPlugin { // with this method you will pass plugin a reference to host void Init(IHostController host); void Start(); void Stop(); } // helper class to combine app domain and loader together public class PluginInfo { public AppDomain Domain { get; set; } public RemoteLoader Loader { get; set; } } 

Now the RemoteLoader is slightly rewritten (for me this did not work):

 public class RemoteLoader : MarshalByRefObject { private Assembly _pluginAassembly; private IPlugin _instance; private string _name; public void Init(IHostController host, string assemblyPath) { // note that you pass reference to controller here _name = Path.GetFileNameWithoutExtension(assemblyPath); if (_pluginAassembly == null) { _pluginAassembly = AppDomain.CurrentDomain.Load(File.ReadAllBytes(assemblyPath)); } // Required to identify the types when obfuscated Type[] types; try { types = _pluginAassembly.GetTypes(); } catch (ReflectionTypeLoadException e) { types = e.Types.Where(t => t != null).ToArray(); } var type = types.FirstOrDefault(t => t.GetInterface("IPlugin") != null); if (type != null && _instance == null) { _instance = (IPlugin) Activator.CreateInstance(type, null, null); // propagate reference to controller futher _instance.Init(host); } } public string Name => _name; public bool IsStarted { get; private set; } public void Start() { if (_instance == null) { return; } _instance.Start(); IsStarted = true; } public void Stop() { if (_instance == null) { return; } _instance.Stop(); IsStarted = false; } } 

And host:

 // note : inherits from MarshalByRefObject and implements interface public class HostController : MarshalByRefObject, IHostController { private readonly Dictionary<string, PluginInfo> _plugins = new Dictionary<string, PluginInfo>(); public void ScanAssemblies(params string[] paths) { foreach (var path in paths) { var setup = new AppDomainSetup(); var domain = AppDomain.CreateDomain(Path.GetFileNameWithoutExtension(path), null, setup); var assemblyPath = Assembly.GetExecutingAssembly().Location; var loader = (RemoteLoader) domain.CreateInstanceFromAndUnwrap(assemblyPath, typeof (RemoteLoader).FullName); // you are passing "this" (which is IHostController) to your plugin here loader.Init(this, path); _plugins.Add(loader.Name, new PluginInfo { Domain = domain, Loader = loader }); } } public string[] Plugins => _plugins.Keys.ToArray(); public void StartPlugin(string name) { if (_plugins.ContainsKey(name)) { var p = _plugins[name].Loader; if (!p.IsStarted) { p.Start(); } } } public void StopPlugin(string name) { if (_plugins.ContainsKey(name)) { var p = _plugins[name].Loader; if (p.IsStarted) { p.Stop(); } } } } 

Now create two different assemblies. Each of them only needs to bind the IPlugin and IHostController interfaces. In the first build, define the plugin:

 public class FirstPlugin : IPlugin { const string Name = "First Plugin"; public void Init(IHostController host) { Console.WriteLine(Name + " initialized"); } public void Start() { Console.WriteLine(Name + " started"); } public void Stop() { Console.WriteLine(Name + " stopped"); } } 

In the second build, define another plugin:

 public class FirstPlugin : IPlugin { const string Name = "Second Plugin"; private Timer _timer; private IHostController _host; public void Init(IHostController host) { Console.WriteLine(Name + " initialized"); _host = host; } public void Start() { Console.WriteLine(Name + " started"); Console.WriteLine("Will try to restart first plugin every 5 seconds"); _timer = new Timer(RestartFirst, null, 5000, 5000); } int _iteration = 0; private void RestartFirst(object state) { // here we talk with a host and request list of all plugins foreach (var plugin in _host.Plugins) { Console.WriteLine("Found plugin " + plugin); } if (_iteration%2 == 0) { Console.WriteLine("Trying to start first plugin"); // start another plugin from inside this one _host.StartPlugin("Plugin1"); } else { Console.WriteLine("Trying to stop first plugin"); // stop another plugin from inside this one _host.StopPlugin("Plugin1"); } _iteration++; } public void Stop() { Console.WriteLine(Name + " stopped"); _timer?.Dispose(); _timer = null; } } 

Now in your main .exe that contains all the plugins:

 static void Main(string[] args) { var host = new HostController(); host.ScanAssemblies(@"path to your first Plugin1.dll", @"path to your second Plugin2.dll"); host.StartPlugin("Plugin2"); Console.ReadKey(); } 

And the result:

 First Plugin initialized Second Plugin initialized Second Plugin started Will try to restart first plugin every 5 seconds Found plugin Plugin1 Found plugin Plugin2 Trying to start first plugin First Plugin started Found plugin Plugin1 Found plugin Plugin1 Found plugin Plugin2 Trying to stop first plugin Found plugin Plugin2 Trying to stop first plugin First Plugin stopped First Plugin stopped Found plugin Plugin1 Found plugin Plugin2 Trying to stop first plugin 
+3
source

You can connect the plugin to the host to complete these steps. You can pass RemoteLoader instance of the MarshalByRefObject derived class created by the host. RemoteLoader can then use this instance to perform any action.

You can also connect plugins to each other by passing the appropriate MarshalByRefObject from the host to each plugin. I would recommend routing all actions through the host, though, because it is a simpler architecture.

+3
source

All Articles