Hallo!
In diesem Thread stelle ich euch das
Plugin-System V. 3.1.0
vor, und ich muss sagen, dass es mir sehr viel Denkarbeit gekostet hat.
Und Denkarbeit ist schwierig - darum hat das Denken letztendlich mehrere Wochen gebraucht, das implementieren dann aber nur 2 Tage.
Crosspost bei Delphi-Treff
Crosspost bei Delphi-Forum
Ach ja: zwischendurch gab es auch das Plugin-System Version 2.
Bei dieser Version hat mir die Interface-Referenzzählung aber einen gewaltigen Strich durch die Rechnung gezogen, darum habe ich es verworfen.
Das Plugin-System Version 1 findet ihr bei Delphi-Treff, ich werde es aber hier nicht verlinken, da es veraltet ist und ich es nicht mehr unterstütze.
WICHTIG:
Bei Unklarheiten bitte sofort Fragen - egal wie dumm die Fragen sind!
Allgemeine Informationen
Entwickler: Henning Dieterichs
Lizenz: Dieses System kann frei verwendet werden, ich würde mich aber darüber freuen, wenn ich bei Benutzung von diesem Plugin-System namentlich erwähnt werde!
Getestete Umgebung: Windows XP, Delphi 7 (mit früheren Version nicht getestet!).
Benötigte Fremdkomponenten: Keine.
Download
Ganz kurz vorweg: Download gibts
hier (mit 9 Demos).
Größe ca. 100 KiB.
Hier gibts das ganze mit bereits kompilierten DLLs und Exen.
Größe ca. 3,5 MiB.
Alle benötigten Dateien sind jeweils im Archiv enthalten
Wichtigste Features (Insider-Wissen)- Client <-> Host und Client <-> Client Datenaustausch möglich
- Sprachunabhängig
- Objekt-Export
- Vererbung, Überschreibung exportierter Objekte
- Klassen-Export, Klassen-Methoden
- Flexible Plugin-Quelle durch Plugin-Packages (z.B. aus DLLs)
Was macht dieses Plugin-System?
Dieses Plugin-System ist dazu dar, ein Programm zu schreiben, welches nachher beliebig erweitert werden kann.
Dabei können vorhandene Module überschrieben, erweitert und verbessert werden, ohne das an ihnen etwas verändert werden muss.
Für wen ist dieses Plugin-System geeignet?
Im Grunde genommen für jeden, der es sich zutraut, sich mit den Interfaces von Delphi rumzuschlagen - der Einstieg ist sehr kompliziert, das Ergebnis dafür aber um so besser.
Dieses Plugin-System kann für kleinere Programme als auch für größere verwendet werden - auch wenn das System nicht ganz ausgenutzt wird.
Dadurch können fremde Programme durch andere Entwickler spielend leicht verbessert oder nach ihren Wünschen angepasst werden.
So kann es beispielsweise ein Plugin geben, welches einem Programm ein Tray-Icon hinzufügt - dabei spielt es letzendlich keine Rolle, um welches Programm es sich handelt, solange es mit dem Plugin-System ausgestattet ist.
Wie kann das funktionieren?
Ganz einfach - über Interfaces.
Die gesamte Kommunikation innerhalb des Plugin-Systems baut nur auf Interfaces auf, weshalb das gesamte System sogar Programmiersprachen-Unabhängig ist.
Allerdings besitzen die Interfaces von Delphi eine sehr unangenehme Eigenschaft: die Referenzzählung.
Warum die so blöd ist, kann
hier und
hier nachgelesen werden.
Das Problem habe ich letztendlich mit Pointer auf Interfaces in den Griff bekommen - bei ihnen ignoriert Delphi die Referenzzählung.
Allerdings muss dazu einiges beachtet werden:
Die PInterfaces (Pointer auf Interfaces) dürfen nicht (bzw. wenn, dann nur, wenn man weiß, was man tut) dereferenziert werden.
Wenn ein PInterface in ein anderes gewandelt werden soll, kann die Klasse TIntf dazu benutzt werden - sie verwaltet das dann.
Aufbau des Plugin-Systems
Begriffe
Damit die Erklärung einfacher wird, habe ich mir einige Begriffe einfallen lassen, bzw. sie mit einer Bedeutung belegt:
Plugin: Das Plugin ist die kleinste Einheit: es stellt ein Objekt bzw. seine Klasse dar
Package:Ein
Package ist eine Ansammlung von Plugins
Host: Der Host ist das Modul, das die Packages verwaltet.
Client: Der Client ist das Gegenstück und stellt ein
Package dar, die Multiplizität zum Host beträgt N:1
Library: Der Host verwaltet alle Packages in einer gemeinsamen Library (= Bücherei)
Units
Das Plugin-System besteht hauptsächlich aus 4 Units:
plgHeader (die wichtigste
Unit)
plgCore (enthält allgemeine Klasse)
plgLibrary (für den Host)
plgPackage (für den Client)
Client (als DLL)
- Erstellen eines Plugins
Erst ein Plugin enthält ausführbaren Code und ist somit das wichtigste Element in diesem System.
-- Festlegen des Interfaces
Zuerst muss das Interface festgelegt werden, welches das Plugin implementiert. Jedes Interface muss eine
GUID besitzen.
Ein Plugin kann auch mehrere Interfaces implementieren.
Beispiel:
Delphi-Quellcode:
IDemo = interface
['{AD1D916F-6AD4-4221-BBCC-1960A30110DB}']
procedure WriteSth;
end;
-- Implementieren des Interfaces
Nachdem die zu implementierenden Interfaces festgelegt wurden, können sie von einem oder mehreren Plugins implementiert werden.
Für jedes Plugin wird dazu eine neue Klasse erstellt, die von der Klasse TBasePlugin und dem Interface erbt.
Beispiel:
Delphi-Quellcode:
TDemo = class(TBasePlugin, IDemo)
public
procedure WriteSth;
end;
{ TDemo }
procedure TDemo.WriteSth;
begin
WriteLn('Hello World!');
end;
Das erste Plugin ist fertig - jetzt muss es nur noch in das
Package eingebunden werden.
- Zusammenstellung des Packages
-- Einbinden der Package-Unit
Damit das
Package benutzt werden kann, muss erstmal die
Unit plgPackage eingebunden werden.
Diese
Unit verwaltet ein
Package-Objekt und exportiert automatisch eine Funktion, die das
Package-Objekt exportiert.
Beispiel:
Delphi-Quellcode:
uses
[...], plgPackage, [...];
-- Konfigurieren des Packages
Der Host sollte wissen, mit welchem
Package er es zu tun hat.
Es hängt aber auch nur vom Host ab, ob er unkonfigurierte Clients zulässt.
Es ist aber trotzdem immer besser, ein
Package mit einer Version, Reversion und einer eindeutigen
GUID zu versehen, damit es identifiziert werden kann.
Außerdem sollte auch der Autor angegeben werden.
Beispiel:
Delphi-Quellcode:
PluginPackage.Version := 1;
PluginPackage.Reversion := 0;
PluginPackage.Author := 'Henning';
PluginPackage.GUID := StringToGUID('{65FE93FD-3E7C-464F-9A95-1B66EEE3557C}');
Hinweis: eine eindeutige
GUID kann in Delphi mit der Tastenkombination Strg+Shift+G erzeugt werden.
-- Befüllen des Packages mit Plugins
Anschließend kann das
Package mit Plugins befüllt werden.
Dazu wird zu dem Plugin eine Plugin-Klasse erzeugt, die die Informationen zu dem Plugin bereithält.
Diese sind einmal der Namespace und Name des Plugins.
Der Namespace bestimmt die Gültigkeit des Namens - zwei Plugins mit selben Namen aber unterschiedlichen Namespaces gehören nicht zusammen.
Der Namespace verringert also die Gefahr von unbeabsichtigten Namenskonflikten.
Dann muss jedem Plugin ebenfalls eine eindeutige
GUID zugeordnet werden, sodass es innerhalb des Plugin-Systems identifizierbar bleibt.
Der Parameter Data (der letzte Parameter, im unten stehendem Beispiel nicht angegeben) ist optional und kann weggelassen werden.
Beispiel:
PluginPackage.AddPluginClass(TCustomPluginClass.Create(TDemo, 'demo', 'demo1', StringToGUID('{11139406-15BF-4769-87B6-5195A0AC6020}')));
Hinweis: es können beliebig viele Plugins zu einem
Package zusammengefasst werden.
- Fertig! Der Client ist jetzt eingerichtet!
Die
DLL kann jetzt vom Host geladen werden.
Host
- Festlegen der Interfaces
Damit der Host die Plugins bedienen kann, müssen ihm die implementierten Interfaces bekannt sein.
Sie können aber so abstrakt definiert sein, dass sie eine große Möglichkeit an Erweiterungen bieten.
- Erzeugen der PluginLibrary
Damit der Host PluginPackages laden kann, benötigt er erstmal eine PluginLibrary.
Diese ist in der
Unit plgLibrary enthalten und muss manuell erzeugt und am Ende wieder freigegeben werden.
Beispiel:
Delphi-Quellcode:
uses
[...], plgLibrary, [...];
[...]
var
PluginLib: TPluginLibrary;
begin
PluginLib := TPluginLibrary.Create;
try
[...]
finally
PluginLib .Free;
end;
end;
- Einbinden der Packages
Schon jetzt können die Packages der Clients eingebunden werden.
Beispiel:
Delphi-Quellcode:
[...]
try
PluginLib.AddPackage(TDLLLibPlgPackage.Create('projClient.dll', PluginLib));
finally
[...]
Dabei wird eine Klasse erzeugt, die das
Package aus der
DLL wrappt.
Auf diese Weise ist es auch Möglich, die Packages aus anderen Quellen zu beziehen (weiteres in den Demos).
- Benutzung der darin enthaltenen Plugins
Jetzt geht es los - die Plugin-Klasse kann herausgesucht und erzeugt werden! Dabei kommen jetzt die PInterfaces ins spiel, also Vorsicht!
Beispiel:
Delphi-Quellcode:
var
DemoObj : TIntf;
[...]
DemoObj := TIntf.ApplyPIntf(PluginLib.PlgClasses['demo', 'demo1'].CreatePlugin([]));
[...]
Um jetzt das Interface aus diesem DemoObj zu erhalten muss erstmal eine weitere Variable angelegt werden.
Beispiel:
Delphi-Quellcode:
var
PIDemoIntf: ^IDemo; //Pointer auf das Interface IDemo
[...]
Über DemoObj kann diese Variable befüllt werden:
Delphi-Quellcode:
[...]
DemoObj.GetPIntf(IDemo, PIDemoIntf);
[...]
Jetzt kann die Funktion WriteSth aufgerufen werden:
Delphi-Quellcode:
[...]
PIDemoIntf.WriteSth;
[...]
Und zum Schluss darf nicht vergessen werden, das Demo-Plugin wieder freizugeben:
Delphi-Quellcode:
[...]
DemoObj.Free;
[...]
Fertig! Das erste Plugin-System wurde benutzt und kann jetzt auch mit viel komplexeren Plugins bestückt werden! (siehe Demos)
Wie am Anfang gesagt:
Es ist ganz wichtig, das ihr, falls ihr Fragen habt, fragt!
Also kurz gesagt:
Bei Fragen: fragen!
In den Demos wird auch noch vieles klarer und verständlicher.
Sie sind nach ihrem Schwierigkeitsgrad geordnet und die letzten beiden enthalten nochmal (fast) alles zusammen.