首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在插件之间进行通信?

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

Stack Overflow用户
提问于 2016-05-29 11:56:11
回答 2查看 1.4K关注 0票数 6

我有一个插件系统,在这个系统中,我使用MarshalByRefObject来创建每个插件的独立域,这样用户就可以重新加载他们的新版本,这是他们认为合适的,而不必关闭主应用程序。

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

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

代码语言:javascript
复制
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);
        }
    }
}

所以我就可以,例如:

代码语言:javascript
复制
var domain = AppDomain.CreateDomain(Name, null, AppSetup);
var assemblyPath = Assembly.GetExecutingAssembly().Location;
var loader = (RemoteLoader)Domain.CreateInstanceFromAndUnwrap(assemblyPath, typeof(RemoteLoader).FullName);
loader.Start();

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

然后,在我的包装上,我有这样的方法:

代码语言:javascript
复制
bool Start(string name);
bool Stop(string name);

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

代码语言:javascript
复制
List<Plugin> Plugins

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

我不明白的是,如何实现下面的,从内部插件。能够:

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

或者,考虑到插件是孤立的,或者我需要打开一个不同的通信路径来实现这一点,那么这甚至可以用MarshalByRefObject实现吗?

为了获得赏金,我正在寻找上面描述的一个可行的可验证的例子.

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-06-02 09:52:07

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

代码语言:javascript
复制
// 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 (对我来说不起作用):

代码语言:javascript
复制
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;
    }
}

一位主持人:

代码语言:javascript
复制
// 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();
            }
        }
    }
}

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

代码语言:javascript
复制
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");
    }
}

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

代码语言:javascript
复制
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中,它承载了所有插件:

代码语言:javascript
复制
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();
}

产出如下:

代码语言:javascript
复制
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
票数 4
EN

Stack Overflow用户

发布于 2016-05-29 14:10:24

您可以让一个插件要求它的主机执行这些操作。您可以向RemoteLoader传递由主机创建的MarshalByRefObject派生类的实例。然后,RemoteLoader可以使用该实例来执行任何操作。

您还可以通过将一个合适的MarshalByRefObject从主机传递给每个插件,从而使插件彼此通信。不过,我建议通过主机路由所有操作,因为它是一个更简单的体系结构。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/37509610

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档