如何在插件之间进行通信?

时间:2016-05-29 11:56:11

标签: c# marshalbyrefobject

我有一个插件系统,我使用MarshalByRefObject为每个插件创建隔离域,因此用户可以重新加载他们认为合适的新版本,而无需关闭主应用程序。

现在我需要允许插件查看当前正在运行的插件,并且可能启动/停止特定的插件。

我知道如何从包装器发出命令,例如在下面的代码中:

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);
        }
    }
}

那么我可以,例如:

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

当然以上只是一个恢复的样本......

然后在我的包装上我有以下方法:

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

这基本上是从列表中发出特定插件的开始/停止的包装器,以及用于跟踪正在运行的插件的列表:

List<Plugin> Plugins

插件只是一个包含DomainRemoteLoader信息等的简单类。

我不明白,如何从插件内部实现以下目标。能够:

  • 查看正在运行的插件列表
  • 执行特定插件的“开始”或“停止”

或者,如果插件被隔离,或者我必须打开不同的通信路径来实现此目的,那么MarshalByRefObject甚至可以实现这一点吗?

对于赏金,我正在寻找上述......

的工作可验证的例子

2 个答案:

答案 0 :(得分:3)

您可以制作一个插件,让它的主持人执行这些操作。您可以将g_text = '@' + text; $.ajax({ url: '/pages/profile/actions.php?action=isusername', method: 'POST', data: { uname: text, } }).done(function(result) { if (result == "true") { g_text = '<a target="_blank" href="/?page=profile&base=' + text + '">' + '@' + text + '</a>'; } }); return g_text; 由主机创建的RemoteLoader派生类的实例传递给MarshalByRefObject。然后RemoteLoader可以使用该实例执行任何操作。

您还可以通过从主机向每个插件传递合适的MarshalByRefObject来使插件相互通信。不过,我建议通过主机路由所有操作,因为它是一种更简单的架构。

答案 1 :(得分:3)

首先让我们定义几个接口:

// 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; }
}

现在有点重写了RemoteLoader(对我来说不起作用):

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;
    }
}

主持人:

// 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();
            }
        }
    }
}

现在让我们创建两个不同的程序集。他们每个人只需要引用 interfaces IPlugin和IHostController。在第一个程序集中定义插件:

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");
    }
}

在第二个程序集中定义另一个插件:

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;
    }
}

现在在您的主.exe中托管所有插件:

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();
}

输出是:

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