Zulassen, dass C # -Plugins auf Anwendung-Hooks registriert werden

8

Ich baue eine .NET-basierte Anwendung und möchte ein erweiterbares und steckbares Design ermöglichen.

Der Einfachheit halber bietet die Anwendung eine Reihe von Operationen und Ereignissen:

  • DoSomething ()
  • DoOtherthing ()
  • OnError
  • OnSuccess

Ich würde gerne "plugins" anbieten, die geladen und in einige dieser Operationen eingebunden werden (so etwas wie: Wenn Event1 ausgelöst wird, starte plugin1).

Zum Beispiel: Führen Sie plugin1.HandleError () aus, wenn das Ereignis OnError ausgelöst wird.

Dies kann einfach mit dem Ereignisabonnement erfolgen:

%Vor%

Das Problem ist das:

  1. Meine App kennt den Typ "Plugin1" nicht (es ist ein Plugin, meine App bezieht sich nicht direkt darauf).
  2. Dadurch wird das Plugin vor der Zeit instanziiert, was ich nicht mache möchte tun.

In "traditionellen" Plugin-Modellen lädt und führt die Anwendung ("Client" von Plugins) den Plugin-Code an bestimmten Schlüsselpunkten aus. Zum Beispiel - eine Bildbearbeitungsanwendung, wenn eine bestimmte Operation ausgeführt wird.

Die Kontrolle darüber, wann der Plugin-Code instanziiert werden soll und wann er ausgeführt werden muss, ist der Client-Anwendung bekannt.

In meiner Anwendung entscheidet das Plugin selbst, wann es ausgeführt werden soll ("Plugin sollte sich beim OnError-Ereignis registrieren").

Wenn der "Ausführungs" -Code des Plugins neben dem "Registrierungs" -Code gehalten wird, stellt sich ein Problem, dass die Plugin-DLL mit zur Registrierungszeit in den Speicher geladen wird, was ich verhindern möchte.

Wenn ich zum Beispiel eine Register () -Methode in der Plugin-DLL hinzufüge, muss die Plugin-DLL in den Speicher geladen werden, damit die Register-Methode aufgerufen wird.

Was könnte eine gute Designlösung für dieses spezielle Problem sein?

  • Faules Laden von Plugin-DLLs (oder das Anbieten von Lazy / Eager Loading).
  • Plugins erlauben, zu steuern, an welchen verschiedenen Teilen des Systems / der App sie sich einhaken.
lysergic-acid 24.04.2012, 22:11
quelle

4 Antworten

5

Sie versuchen ein nicht existierendes Problem zu lösen. Das mentale Abbild aller Typen, die geladen werden, wenn Ihr Code Assembly.LoadFrom () aufruft, ist falsch. Das .NET-Framework nutzt die Vorteile von Windows als bedarfsorientiertes virtuelles Speicher-Betriebssystem. Und dann einige.

Der Ball rollt, wenn Sie LoadFrom () aufrufen. Das macht die CLR zum Erstellen einer Memory-Mapped-Datei , einer Kern-Betriebssystem-Abstraktion. Es aktualisiert ein wenig internen Status, um eine Assembly zu verfolgen, die sich jetzt in der AppDomain befindet, es ist sehr geringfügig. Das MMF legt die Speicherzuordnung fest und erstellt virtuelle Speicherseiten, die den Inhalt der Datei abbilden. Nur ein kleiner Deskriptor im TLB des Prozessors. Nichts wird tatsächlich aus der Assemblydatei gelesen.

Als Nächstes versuchen Sie mit Reflection, einen Typ zu finden, der eine Schnittstelle implementiert. Dies führt dazu, dass die CLR einige der Assembly-Metadaten aus der Assembly liest. An diesem Punkt führen Seitenfehler dazu, dass der Prozessor den Inhalt einiger Seiten, die den Metadatenabschnitt der Baugruppe abdecken, in den RAM abbildet. Eine Handvoll Kilobytes, möglicherweise mehr, wenn die Assembly viele Typen enthält.

Als nächstes tritt der Just-in-Time-Compiler in Aktion, um Code für den Konstruktor zu generieren. Das verursacht den Prozessor, die Seite, die das Konstruktor IL enthält, in RAM zu schreiben.

Etcetera. Kernidee ist, dass Baugruppeninhalte immer nur langsam gelesen werden, nur wenn benötigt . Dieser Mechanismus ist nicht anders für Plugins, sie funktionieren genauso wie die normalen Assemblys in Ihrer Lösung. Mit dem einzigen Unterschied, dass die Reihenfolge etwas anders ist. Sie laden zuerst die Assembly und dann sofort den Konstruktor. Im Gegensatz zum Aufruf des Konstruktors eines Typs in Ihrem Code und der CLR wird die Assembly sofort geladen. Es dauert genauso lange.

    
Hans Passant 25.04.2012 00:05
quelle
4

Sie müssen den Pfad der DLL finden und daraus ein Assembly-Objekt erstellen. Von dort müssen Sie Klassen abrufen, die Sie abrufen möchten (z. B. alles, was Ihre Schnittstelle implementiert):

%Vor%

Natürlich können Sie von hier aus alles tun, was Sie wollen, Methoden verwenden, Veranstaltungen abonnieren usw.

    
Oskar Kjellin 24.04.2012 22:15
quelle
2

Beim Laden von Plug-in-Assemblys lehne ich mich an meinen IoC-Container, um die Assemblys aus einem Verzeichnis zu laden (ich verwende StructureMap), obwohl Sie sie wie in @ Oskars Antwort manuell laden können.

Wenn Sie das Laden von Plugins während der Ausführung Ihrer Anwendung unterstützen wollten, kann StructureMap "neu konfiguriert" werden und somit neue Plugins aufnehmen.

Für Ihre Anwendungs-Hooks können Sie Ereignisse an einen Event-Bus senden. Im folgenden Beispiel wird StructureMap verwendet, um alle registrierten Ereignishandler zu finden. Sie können jedoch auch eine einfache alte Reflektion oder einen anderen IoC-Container verwenden:

%Vor%

Sie können dann ein Ereignis wie folgt auslösen:

%Vor%

Und handle es (in deinem Plugin zum Beispiel) so:

%Vor%

Ich würde definitiv diesen Beitrag empfehlen Shannon Deminick, die viele Probleme bei der Entwicklung von Pluggable-Anwendungen behandelt. Es ist, was wir als Basis für unseren eigenen "plugin manager" verwendet haben.

Ich persönlich würde vermeiden, die Baugruppen bei Bedarf zu laden. IMO ist es besser, eine etwas längere Startzeit (noch weniger ein Problem in einer Webanwendung) zu haben, als Benutzer der laufenden Anwendung warten müssen, bis die notwendigen Plugins geladen sind.

>     
Ben Foster 24.04.2012 22:32
quelle
1

Würde das Managed Extensibility Framework Ihren Anforderungen entsprechen?

    
mgnoonan 24.04.2012 22:52
quelle

Tags und Links