AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Projekte Plugin-System Version 3

Plugin-System Version 3

Ein Thema von Henning der zweite · begonnen am 19. Jul 2009 · letzter Beitrag vom 15. Mai 2011
Antwort Antwort
Seite 1 von 3  1 23   
Henning der zweite
Registriert seit: 10. Mär 2008
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.
 
Benutzerbild von himitsu
himitsu

 
Delphi 12 Athens
 
#2
  Alt 20. Jul 2009, 00:05
statt pointer auf Interfaces, hätte ich eher die Referenzzählung selber verändert

Zitat:
Warum die so blöd ist, kann hier und hier nachgelesen werden.
HIER und im DF hast du die Links vergessen


Zitat:
Kann die Referenzzählung eines Interfaces komplett deaktiviert werden? (Ich meine nicht nur die Zählung an sich)
Also so, dass der Compiler eine Interface-Referenz wie einen normalen Pointer behandelt (also ohne automatischen Aufruf von _AddRef und _Release)?
ja kann.
und zwar wenn du selber das Interface implementierst, kannst du _AddRef und _Release selbst erstellen und darin einfach nicht zählen ... nun noch eine Methode ala .Free im Interface implementieren und darin dann nur noch das Object freigeben. (hört sich schwerer an, als es ist )
  Mit Zitat antworten Zitat
Henning der zweite

 
Delphi 5 Standard
 
#3
  Alt 20. Jul 2009, 10:34
Zitat:
statt pointer auf Interfaces, hätte ich eher die Referenzzählung selber verändert
Das geht ja nicht.

Zitat:
HIER und im DF hast du die Links vergessen
Oh, ja, tut mir leid

Hier und hier nochmal explizit die Links.

Zitat:
ja kann.
Nö, geht nicht.

Das Problem ist nämlich ein anderes: wenn Delphi das übernimmt, ruft es, wenn die Variable "out of Scope" geht, die Methode _Release auf. Und da gegen kann ich nichts tun.
Ist das Objekt bereits freigegeben (z.B. weil die DLL aus dem Speicher entfernt wurde), ruft Delphi _Release eines freigegebenen Objekts auf - und dann knallts.
Da Delphi aber einige unsichtbare Kopien der Variablen hält, wird das dann zum echten Problem.
(Das kann auch nochmal in den verlinkten Threads nachgelesen werden)

//Edit: (hab vergessen mich zu bedanken)
Danke, dass du meinen Text so aufmerksam durchgelesen hast! (das mit den Links wäre mir nicht aufgefallen )
Henning D.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

 
Delphi 12 Athens
 
#4
  Alt 20. Jul 2009, 10:49
Zitat von Henning der zweite:
Das Problem ist nämlich ein anderes: wenn Delphi das übernimmt, ruft es, wenn die Variable "out of Scope" geht, die Methode _Release auf. Und da gegen kann ich nichts tun.
mußt/darfst du auch nicht, denn diese Methode ruft jede "Programmiersprache" auf, wenn da eine Variable ala Interface-Referenz entfernt/freigeeben wird.
Zitat von Henning der zweite:
Ist das Objekt bereits freigegeben (z.B. weil die DLL aus dem Speicher entfernt wurde), ruft Delphi _Release eines freigegebenen Objekts auf - und dann knallts.
wie gesagt, dann gib es doch nicht frei

ich hätte nichts gesagt, wenn es nicht ginge

du darfst dann nur dein Objekt nicht von TInterfacedObject oder einem seiner Nachfolger ableiten,
sondern mußt die Methoden für IInterface/IUnknown selber implementieren, bzw. diese Basisklasse verwenden:
Delphi-Quellcode:
// Copyright (c) 1997-2009 FNS Enterprize's™
// 2003-2009 himitsu @ Delphi-PRAXiS

type
  //INonRefCountInterface = interface
  // ['{00000000-0000-0000-C000-000000000046}']
  // function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  // function _AddRef: Integer; stdcall;
  // function _Release: Integer; stdcall;
  // procedure Free; stdcall;
  //end;

  // oder in Kurz
  INonRefCountInterface = interface(IInterface)
    procedure Free; stdcall;
  end;

  TNonRefCountInterfacedObject = class(TObject, INonRefCountInterface)
  protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure Free; stdcall;
  end;

function TNonRefCountInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TNonRefCountInterfacedObject._AddRef: Integer;
begin
  Result := 1;
end;

function TNonRefCountInterfacedObject._Release: Integer;
begin
  Result := 1;
end;

procedure TNonRefCountInterfacedObject.Free;
begin
  inherited Free;
end;
über die zusätzliche Metode Free kann/muß man das Objekt gezielt freigeben,
denn von alleine macht es das ja nun nimmer

PS: falls man das Freigeben von innerhalb des Objectes löst, kann man .Free auch weglassen;


(OK, das (c) kann man ignorieren .... ist ja nix Neus/Weltbewegendes ... aber ich hatte grad Lust drauf)
  Mit Zitat antworten Zitat
Henning der zweite

 
Delphi 5 Standard
 
#5
  Alt 20. Jul 2009, 11:07
Zitat:
mußt/darfst du auch nicht, denn diese Methode ruft jede "Programmiersprache" auf, wenn da eine Variable ala Interface-Referenz entfernt/freigeeben wird.
Dann lese dir mal den Thread bei Delphi-Treff durch, da habe ich das auch durchdiskutiert.

In diesen Thread - so denke ich jedenfalls - gehört diese Diskussion nicht.

Zitat:
du darfst dann nur dein Objekt nicht von TInterfacedObject oder einem seiner Nachfolger ableiten,
sondern mußt die Methoden für IInterface/IUnknown selber implementieren, bzw. diese Basisklasse verwenden:
Schon klar, so habe ich es auch gemacht (siehe Quelltext des Plugin-Systems).

Bzw. damit sich die Diskussion hat, das Problem nochmal auf den Knackpunkt gebracht:

(abgekürzter Delphi-Code)
In der DLL:
Delphi-Quellcode:
func ExportIntf: IIntf; //exportiert
  Result := TITestImpl.Create;
In der Anwendung:
Delphi-Quellcode:
DLL.LoadLib;
Intf := DLL.ExportIntf as ITest;
Intf := nil;
DLL.FreeLib;
In dem Beispiel knallt es am Ende (ganz am Ende der Prozedur). Warum? Weil bei dem as-Cast eine temporäre Interface-Referenz entsteht, von der erst am Ende _Release aufgerufen wird - zu diesem Zeitpunkt wurde die DLL mitsamt des Objekts aber schon längst aus dem Speicher entfernt -> es knallt.
Henning D.
  Mit Zitat antworten Zitat
axellang

 
Delphi XE2 Enterprise
 
#6
  Alt 20. Jul 2009, 12:52
Hallo Henning,

wollte mir mal das PlgSystem mal ansehen da ich z.Z. selbst was mit Plugins mache jedoch fehlt wohl eine Unit "hdInterfaceMisc" und ohne die geht nichts.
Überprüfe mal das Paket.

Axel
Alexander Lang
  Mit Zitat antworten Zitat
Henning der zweite

 
Delphi 5 Standard
 
#7
  Alt 20. Jul 2009, 14:04
Ups, hab ich wohl vergessen hinzuzufügen
Hier ist der direkte Download der Unit - oben hab ichs auch nochmal verlinkt.
Henning D.
  Mit Zitat antworten Zitat
axellang

 
Delphi XE2 Enterprise
 
#8
  Alt 20. Jul 2009, 15:27
Salü,

Zitat von Henning der zweite:
Ups, hab ich wohl vergessen hinzuzufügen
Hier ist der direkte Download der Unit - oben hab ichs auch nochmal verlinkt.
kann passieren.

Axel
Alexander Lang
  Mit Zitat antworten Zitat
axellang

 
Delphi XE2 Enterprise
 
#9
  Alt 21. Jul 2009, 12:46
Hallo Henning,

habe etwas Zeit gefunden (Mittag) um Dein PlgSystem zu testen. Doch leider fehlt noch eine Unit und zwar die "DLLUtils".
Schade, besorge Dir ne XP/Vista/W7 VM und installiere Delphi und dann kannst Du die Komponenten testen bevor Du die weitergibst.

Ne 120 Tage Version findest Du bei Microsoft (als VPC) Microsoft und falls die Delphi Lizenzen ausgehen dann einfach aufstocken Embarcadero.

Axel
Alexander Lang
  Mit Zitat antworten Zitat
Henning der zweite

 
Delphi 5 Standard
 
#10
  Alt 21. Jul 2009, 13:19
@axellang:
Das tut mir leid, das ich deine Zeit vergeudet habe

Es ist mein erstes OpenSource Projekt, das ich veröffentlicht habe, darum wusste ich noch nicht, was alles auf mich zu kommt, und welche Fehler einem alle passieren können...

Ne 120 Tage Version findest Du bei Microsoft (als VPC) Microsoft und falls die Delphi Lizenzen ausgehen dann einfach aufstocken Embarcadero. Ne VM habe ich schon, ich werds dann testen.
Henning D.
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 3  1 23   

Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:41 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz