2016-05-29 2 views
5

Ich habe ein Plugin-System, wo ich MarshalByRefObject verwenden, um isolierte Domänen pro Plugin zu erstellen, so dass Benutzer ihre neuen Versionen neu laden können, wie sie sehen, ohne die Hauptanwendung ausschalten zu müssen.Wie kann ich zwischen Plugins kommunizieren?

Jetzt muss ich einem Plugin erlauben zu sehen, welche Plugins gerade laufen und vielleicht ein bestimmtes Plugin starten/stoppen.

Ich weiß, wie Befehle von der Hülle zu geben, die im folgenden Code zum Beispiel:

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

Also dann könnte ich zum Beispiel:

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

Natürlich ist die oben ist nur ein Probe wieder aufgenommen ...

Dann auf meinem Wrapper habe ich Methoden wie:

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

die im Grunde einen Wrapper ist der Start/Stops eines bestimmten Plugins aus der Liste und eine Liste zur Ausgabe Spur des laufenden Plugins zu halten:

List<Plugin> Plugins 

Plugin ist nur eine einfache Klasse, die Domain, RemoteLoader Informationen hält usw.

Was ich nicht verstehe, ist, wie man das unten erreicht, von innen ein plugin. In der Lage:

  • Ansicht der Liste der laufenden Plugins
  • das Ausführen Start oder Stopp für ein bestimmtes Plugin

Oder wenn dies überhaupt möglich ist mit MarshalByRefObject angesichts der Plugins sind isoliert oder ich würde müssen Sie einen anderen Kommunikationsweg dafür öffnen?

Für die Prämie ich für ein Arbeits nachprüfbare Beispiel der oben beschriebenen Suche ...

Antwort

3

Zuerst das Paar von Schnittstellen lassen definieren:

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

Jetzt ein bisschen neu geschrieben Remoteloader (nicht für mich arbeiten, wie es war):

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

Und ein Wirt:

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

Jetzt erstellen wir zwei verschiedene Baugruppen.Jeder von ihnen muss nur auf Schnittstellen IPlugin und IHostController verweisen. In ersten Baugruppe definieren 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 zweiter Baugruppe definiert ein weiteres 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; 
    } 
} 

Jetzt in Ihrem Haupt-EXE, die alle Plugins Gastgeber:

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

Und der Ausgang ist:

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 
+0

Ich fühle mich so dumm, es war so einfach und ich konnte nicht verstehen, wie man es von dem, was usr gesagt haben, zu implementieren :(Danke Evk, dass es wirklich einfach macht, das Konzept zu verstehen und umzusetzen. – Guapo

+0

Mann, Evk du stehst die Prämie, ich hätte es für weniger Punkte getan! – usr

+0

Tut mir leid, Kopfgeld bringt mich dazu, meine Meinung zu verlieren und schlimme Dinge zu tun, die ich bereuen werde ... – Evk

3

Sie bereits ein Plugin fragen machen kann ist Gastgeber diese Aktionen auszuführen. Sie können an die RemoteLoader eine Instanz einer MarshalByRefObject abgeleiteten Klasse weitergeben, die vom Host erstellt wird. Die RemoteLoader kann dann diese Instanz verwenden, um eine Aktion auszuführen.

Sie können auch die Plugins miteinander kommunizieren lassen, indem Sie einen geeigneten MarshalByRefObject vom Host an jedes Plugin übergeben. Ich würde jedoch empfehlen, alle Aktionen über den Host zu leiten, da dies eine einfachere Architektur ist.

+0

Hallo, danke für die Ans wer, du meinst wie eine Methode mit einem out-Parameter zu der Liste zum Beispiel? Oder könnten Sie vielleicht ein hypothetisches Beispiel dafür geben? – Guapo

+0

'Klasse HostController: MBRO {public void TalkToOtherPlugin (...); } '. Erstellen Sie das in der Host-Domäne und übergeben Sie es an eine Plugin-Domäne. – usr

+0

Das ist genau der Teil, an dem ich festhalte, nicht zu verstehen, wie man weiter geht, Wie ist deine 'Klasse HostController: MBRO {public void TalkToOtherPlugin (...); } 'anders als zum Beispiel meine' Klasse RemoteLoader: MBRO {public void Start(); } '? Versteh mich nicht falsch Ich verstehe nur nicht, wie es für das Plugin zugänglich wäre, das in dieser isolierten Domäne geladen ist, um diese Methode oder irgendetwas aufzurufen. – Guapo