Nachdem ich gezeigt habe, wie man Erweiterungen (PlugIns) für die Delphi-
IDE erstellen kann, möchte ich dieses Mal zeigen, wie man dieses Konzept in eigenen Anwendungen nutzen kann. Dazu werde ich das Tutorial in mehrere Abschnitte aufteilen.
Der erste Abschnitt zeigt die denkbar einfachste Form ein PlugIn zu erstellen. Diese Methode ist nicht unbedingt sehr flexibel, erlaubt es jedoch, schnell einfache Erweiterungen zu erstellen. Vorteil ist, daß das Thema auch für "Neulinge" auf diesem Bereich recht einfach betrachtet werden kann.
Um dieses zu erreichen, werde ich die erste Variante als "Delphi-Only-Lösung" vorstellen, daß heißt, daß auch die PlugIns mit Delphi erstellt werden müssen.
Code:
[color=#000080][i]HINWEIS:[/i]
Diese Variante benötigt die
Unit ShareMem, welche wiederum voraussetzt, daß die Library [i]BORLNDMM.DLL[/i]
zusammen mit der Anwendung ausgeliefert wid. Diese
DLL befindet sich im Delphi\Bin Verzeichnis und darf
zusammen mit der Anwendung weitergegeben werden. In älteren Delphi-Versionen (3...) ist es möglich, daß die
Borland
DLL einen anderen Namen hat.
Die
Unit ShareMem muß als erstes in der Uses-Klausel des Projektes und aller PlugIns stehen![/color]
PlugIns, wie kommen die eigentlich in die Anwendung
Es gibt verschiedene Möglichkeiten, woran eine Anwendung erkennt, das diese PlugIns zu laden hat. Alle Möglichkeiten setzen eines jedoch voraus: Die Anwendung muß so konzipiert sein, daß diese PlugIns überhaupt unterstützt.
In diesem Beispiel sucht die Anwendung nach der Datei
PlugIns.ini, welche im gleichen Verzeichnis wie die Anwendung liegen muß. Die Ini-Datei sieht wie folgt aus.
Code:
[info]
info1=This file contains a list of all plugins.
info2=each plugin has its own section
info3=each plugin section has to contain a string "type=plugin"
info4=each plugin section has to contain a string "dllname" pointing to the plugin file
[sample01]
type=Plugin[color=#800000](off)[/color]
dllname=plugin01.dll
[sample02]
type=Plugin[color=#800000](off)[/color]
dllname=plugin02.dll
Die
rot dargestellten Bereiche müssen gelöscht werden, damit die PlugIns geladen werden. In den ZIP-Files sind die Plug-Ins so eingestellt, daß diese (per Default) erst einmal nicht geladen werden, damit die Anwendung ohne Plug-Ins getestet werden kann. Wenn die Strings
(off) gelöscht sind, werden die Plug-Ins beim nächsten Start automatisch geladen, dann kann das "neue" Verhalten der Anwendung getestet werden.
Andere Möglichkeiten nach Plug-Ins zu suchen
Oft werden zwei weitere Methoden genutzt, um Plug-Ins zu suchen.
- Die Plug-Ins müssen in der Registry unter einem bestimmten Schlüssel eingetragen werden (z.B. Borland Delphi)
- Die Plug-Ins müssen in einem bestimmten Verzeichnis liegen.
Letztere Methode ist nur bedingt empfehlenswert, da diese voraussetzt, das in dem Verzeichnis ausschließlich Plug-Ins liegen, andere Dateien können zu Problemen führen.
Plug-Ins zulassen
In diesem Beispiel werden wir ausschließlich Plug-Ins zulassen, welche als DLLs geladen werden können. Diese Plug-Ins müssen eine Funktion mit dem Namen
LoadPlugIn exportieren, welche folgende Paramter hat:
function LoadPlugin(Parent: THandle; var PlugIn: TPlugIn): Boolean; export;
Der erste Parameter
Parent erhält den
Handle der Anwendung, welche das Plug-In lädt. Der zweite Paramter
PlugIn kann vom Plug-In genutzt werden, um ein Object zurückzuliefern. Wird keines zurückgeliefert, so wird da Plug-In nicht im Menü angezeigt.
Die Applikation lädt die Plug-Ins mit Hilfe der Win-
API Funtion
LoadLibrary. LoadLibrary liefert einen
Handle auf die geladene Library zurück. Wenn die Library erfolgreich geladen wurde, versucht Applikation die Methode
LoadPlugIn mit Hilfe der Win-
API Funtion
GetProcAddress zu laden.
Code:
[color=#000080][i]HINWEIS:[/i]
Bei dem Export von Funktionen aus DLLs muß Groß- und Kleinschreibung beachtet werden (C++-Standard)!!![/color]
Nach erfolgreichem Ladevorgang der Prozedur wird diese aufgerufen und das Plug-In initialisiert.
Code:
[color=#000080][i]HINWEIS:[/i]
In diesem Beispiel muß sich das PlugIn um die Initialisierung des zurückgelieferten Objektes kümmern.
Die Applikation übernimmt die Zerstörung des Objektes, wenn dieses nicht mehr gebraucht wird.[/color]
Das exportierte Plug-In Objekt
Damit die Applikation nach Bedarf auf das Plug-In zurgreifen kann, muß dieses ein Objekt zurückliefern, welches durch die Applikation erkannt wird. Damit sowohl die Applikation, als auch jedes Plug-In vom gleichen Objekttyp "sprechen", wird dieser in einer gemeinsamen
Unit definiert. Diese
Unit ist dem Download beigefügt (\Shared Units\uPlugInDefinition.pas).
Delphi-Quellcode:
//Auszug aus der Unit
type
TPlugIn =
class
public
constructor Create(aParent: THandle);
// information strings
function GetName:
string;
virtual;
stdcall;
abstract;
function GetAuthor:
string;
virtual;
stdcall;
abstract;
function GetComment:
string;
virtual;
stdcall;
abstract;
// menu data
function GetMenuText:
string;
virtual;
stdcall;
abstract;
function GetEnabled: Boolean;
virtual;
stdcall;
abstract;
// launch the Expert
procedure Execute;
virtual;
stdcall;
abstract;
// event handler
function EventHandler(EventNotification: TEventNotification): TEventAction;
virtual;
stdcall;
abstract;
end;
TLoadPlugIn =
function(Parent: THandle;
var PlugIn: TPlugIn): Boolean;
Einerseits ist hier die abstrakte Definition, welche daß durch jedes Plug-In zu exportierende Objekt zeigt. Dieses Objekt liefert verschiedene Informationen zurück. Unter anderem wird gesteuert, wie sich das Plug-In im Menü darstellt und auf welche Ereigenisse reagiert wird. Desweiteren ist auch das Format der zu exportierenden Funktion
TLoadPlugIn definiert.
Das Plug-In I
Jedes Plug-In, welches interaktiv angesteuert werden kann, muss ein Objekt vom Typ
TPlugIn exportieren. Dieses Objekt muß alle abstrakten Methoden implementieren, welche durch die abstrakte Klasse
TPlugIn definiert sind.
Im ersten Plug-In sieht die Klassendefinition wie folgt aus
Delphi-Quellcode:
type
TPlugIn01 = class(TPlugIn)
public
// information strings
function GetName: string; override; stdcall;
function GetAuthor: string; override; stdcall;
function GetComment: string; override; stdcall;
// menu data
function GetMenuText: string; override; stdcall;
function GetEnabled: Boolean; override; stdcall;
// launch the Expert
procedure Execute; override; stdcall;
// event handler
function EventHandler(EventNotification: TEventNotification): TEventAction;
override; stdcall;
end;
{ TPlugIn01 }
function TPlugIn01.EventHandler(EventNotification: TEventNotification):
TEventAction;
begin
// nothing to do
Result := evnContinue;
end;
...
Alle Methoden werden implementiert. Das erste Plug-In führt keine weiteren Aktionen aus. Wird sein Menüpunkt durch den Nutzer aktiviert, so wird lediglich eine Messagebox angezeigt.
Das Plug-In II
Das zweite Plug-In ist ein wenig komplexer. Es reagiert auf mehrere Ereignisse (File->New, File->Open, File->Save). Dazu wird in der Methode
EventHandler auf entsprechende Ereignisse reagiert.
Delphi-Quellcode:
function TPlugIn02.EventHandler(
EventNotification: TEventNotification): TEventAction;
begin
case EventNotification of
evnFileNew:
if MessageBox(Parent, 'Änderungen verwerfen?', 'Öhm', MB_ICONWARNING or
MB_YESNO) = IDYES then
Result := evnContinue
else
Result := evnAbort;
evnFileOpen:
if MessageBox(Parent, 'Änderungen verwerfen?', 'Öhm', MB_ICONWARNING or
MB_YESNO) = IDYES then
Result := evnContinue
else
Result := evnAbort;
evnFileSave:
begin
MessageBox(Parent, 'Bitte "Speichern als..." wählen', 'Öhm',
MB_ICONINFORMATION or MB_OK);
Result := evnAbort;
end;
else
Result := evnContinue;
end;
end;
Damit das Plug-In auf diese Ereignisse reagieren kann, muß die Applikation das Plug-In informieren, wenn solche Ereignisse auftreten. Dazu stelle ich Ihnen die Methode
RunEvent aus der Applikation vor.
Delphi-Quellcode:
function TForm1.RunEvent(EventNotification: TEventNotification): Boolean;
var
I: Integer;
begin
for I := 0 to High(FPlugIns) do
if FPlugIns[I].PlugIn <> nil then
begin
if FPlugIns[I].PlugIn.EventHandler(EventNotification) = evnAbort then
begin
Result := False;
Exit;
end;
end;
Result := True;
end;
Diese Methode sendet ein Ereignis an alle Plug-Ins. Wenn alle Plug-Ins den Status
evnContinue zurückliefern, dann liefert die Methode
True zurück, ansonsten
False. Diese Methode wird zum Beispiel aufgerufen, wenn der User im Menü
File auf
New klickt.
Delphi-Quellcode:
procedure TForm1.New1Click(Sender: TObject);
begin
if RunEvent(evnFileNew) then
begin
mmoFileContent.Clear;
FFileName := '';
end;
end;
Wenn kein Plug-In geladen ist, wird der aktuelle Inhalt des Memos einfach gelöscht. Wenn das zweite Plug-In geladen ist, fragt dieses den User, ob er die Inhalte des Memos wirklich verwerfen will. Antwortet der mit "nein", liefert das Plug-In II
evnAbort zurück und das Memo wird nicht gelöscht.
Der komplette Code
Der komplette Code für dieses einfache Beispiel kann von Mitgliedern der Delphi-PRAXiS frei runtergeladen werden. Wenn Fragen bestehen, einfach ins Forum posten. Mal schauen...
Der nächste Schritt
Im nächsten Schritt werde ich ein etwas komplexeres Beispiel zeigen. Voraussetzung dafür wird ein Verständnis von Delphi-Interfaces sein. Wann es kommt, steht noch nicht fest
...
...