AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi PlugIns in eigenen Anwendungen
Tutorial durchsuchen
Ansicht
Themen-Optionen

PlugIns in eigenen Anwendungen

Ein Tutorial von sakura · begonnen am 14. Mai 2003 · letzter Beitrag vom 26. Feb 2010
Antwort Antwort
Seite 3 von 4     123 4      
Benutzerbild von sakura
sakura
Registriert seit: 10. Jun 2002
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

......
Angehängte Dateien
Dateityp: zip extandable_application__i___incl._bins_.zip (313,3 KB, 639x aufgerufen)
Dateityp: zip extandable_application__i___excl._bins_.zip (15,5 KB, 282x aufgerufen)
Ich bin nicht zurück, ich tue nur so
 
nitschchedu

 
Delphi 7 Professional
 
#21
  Alt 24. Apr 2006, 11:43
Ok sorry dachte da du davon anfängst das auch gleich einen Link hast Danke erstmal für alles
  Mit Zitat antworten Zitat
nitschchedu

 
Delphi 7 Professional
 
#22
  Alt 4. Mai 2006, 12:07
Noch mal ne frage ! Kann ich das wirklich nicht in DLL machen? Packges sind doch scheiße !
Kann von mir aus auch ein massiver Aufwand werden. Hauptsache es wird so das es Ordentlich ist und Funktioniert. Wäre Tool wenn es eine Möglichkeit gibt.
  Mit Zitat antworten Zitat
Benutzerbild von FriFra
FriFra

 
Delphi 2005 Professional
 
#23
  Alt 4. Mai 2006, 12:11
Es geht schon alles mit dlls zu lösen. Du musst Dir nur mal Gedanken über deine Schnittstellen machen... übergib z.B. keine TFont variable, sondern nur deren Parameter als PChar und Integer...
  Mit Zitat antworten Zitat
nitschchedu

 
Delphi 7 Professional
 
#24
  Alt 4. Mai 2006, 13:18
Sage mal kann ich nicht eigentlich ne Pointer auf die Adresse übergeben ?
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#25
  Alt 4. Sep 2007, 11:33
Ich erstelle in dem Plugin ein Fenster und zeige es mit Show an.
Delphi-Quellcode:
procedure TPlugin01.Execute(XMLString: string);
begin
  inherited;
  frmTestPlugin01 := TfrmTestPlugin01.Create(nil);
  frmTestPlugin01.Label1.Caption := 'Testzeichenfolge: ' + XMLString;
  frmTestPlugin01.Label2.Caption := 'Parenthandle: ' + IntToStr(Self.Parent);
  frmTestPlugin01.Show;
end;
Freigegeben wird das Formular im OnClose-Ereignis des Formulares selber:
Delphi-Quellcode:
procedure TfrmTestPlugin01.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  Self.Free;
end;
Aber wie kann ich jetzt zum einen die Plugin DLL benachrichtigen dass das Fenster geschlossen wurde und wie kann das Plgin die Hostanwendung benachrichtigen? Wie man Nachrichten an das Plugin schickt wird ja im Tutorial demonstriert, aber wie geht der umgekehrte Weg?
Michael
  Mit Zitat antworten Zitat
Benutzerbild von FriFra
FriFra

 
Delphi 2005 Professional
 
#26
  Alt 4. Sep 2007, 11:40
Also ich zeige meine Plugin-Fenster immer modal, dann stellt sich mir diese Frage nicht ... ansonsten müsstest Du onClose die entspr. handles prüfen und diese freigeben.
  Mit Zitat antworten Zitat
Benutzerbild von mirage228
mirage228

 
Delphi 2010 Professional
 
#27
  Alt 4. Sep 2007, 13:07
Du könntest dem Plugin ein "Kommunikation"-Interface(oder -Klasse) beim Laden übergeben, über das die Kommunikation zur Anwendugn laufen könnte...

mfG
mirage228
David F.
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#28
  Alt 4. Sep 2007, 14:41
Na ja, das erste problem besteht schon darin, wie die dpr-Datei der Plugin-DLL das Schliessen des Fensters mitbekommt. Hier wird es erstellt und angezeigt:
Delphi-Quellcode:
procedure TPlugin01.Execute(XMLString: string);
begin
  inherited;
  frmTestPlugin01 := TfrmTestPlugin01.Create(nil);
  frmTestPlugin01.Label1.Caption := 'Testzeichenfolge: ' + XMLString;
  frmTestPlugin01.Label2.Caption := 'Parenthandle: ' + IntToStr(Self.Parent);
  frmTestPlugin01.Show;
end;
Wenn das Formular geschlossen wird, soll auch die Host-Anwendung darüber benachrichtigt werden und das Plugin entladen.

Ich verwende, wie im Tutorial eine Plugin-Klasse, die der Host-Anwendung und dem Plugin bekannt ist:
Delphi-Quellcode:
type
  TEventNotification = (evnClose);
  TEventAction = (evnContinue, evnAbort);

  TPlugin = class(TObject)
  private
    FParent: THandle;
    procedure SetParent(const Value: THandle);
  public
    constructor Create(aParent: THandle);
    function GetName: string; virtual; stdcall; abstract;
    function GetAuthor: string; virtual; stdcall; abstract;
    function GetComment: string; virtual; stdcall; abstract;
    function GetCaption: string; virtual; stdcall; abstract;
    procedure Execute(XMLString: string); virtual; stdcall; abstract;
    function EventHandler(EventNotification: TEventNotification): TEventAction; virtual; stdcall; abstract;
    property Parent: THandle read FParent write SetParent;
  end;

  TLoadPlugin = function(Parent: THandle; var PlugIn: TPlugIn): Boolean;

implementation

{ TPlugIn }

constructor TPlugIn.Create(aParent: THandle);
begin
  inherited Create;
  FParent := aParent;
end;

procedure TPlugIn.SetParent(const Value: THandle);
begin
  FParent := Value;
end;
Michael
  Mit Zitat antworten Zitat
Benutzerbild von mirage228
mirage228

 
Delphi 2010 Professional
 
#29
  Alt 4. Sep 2007, 14:48
Also um das "OnClose"-Event des Forms wirst du erstmal nicht drum herum kommen.
Ich schlage vor:

1.) Im OnClose des Form wird TPlugin über das Schließen benachrichtigt (TPlugin.Notify(Sender: TObject; Action: TMyPluginAction);
--> Dazu muss TPlugin irgendwo innerhalb des Plugin-Forms gespeichert werden (nen eigenes Property, im Tag des Forms - wie auch immer!)
--> Bei TMyPluginAction kannst Du ja noch weitere Ereignisse reinpacken -- oder du machst statt "Notify" eine "TPlugin.PluginFormClosed(Sender: TObject)"-Methode
2.) Die Anwendung überigbt eine Klasse oder einen Methodenzeiger, den dein TPlugin-Objekt speichert und aufruft, sobald ein Ereignis (siehe oben) eingetreten ist.

Wenn ich alles richtig bedacht habe, sollte das so gehen

mfG
mirage228
David F.
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#30
  Alt 6. Sep 2007, 16:57
Wenn ich die Sourcen zum zweiten Teil mit Delphi 6 kompilieren will, bekomme ich folgende fehlermeldungen:
Code:
[Fehler] uFormMain.pas(88): Ausdruckstyp muß BOOLEAN sein
[Fehler] uFormMain.pas(94): Ausdruckstyp muß BOOLEAN sein
[Fehler] uFormMain.pas(100): Ausdruckstyp muß BOOLEAN sein
[Fehler] uFormMain.pas(112): Anweisung erwartet, aber 'PROCEDURE' gefunden
[Fehler] uFormMain.pas(39): Ungenügende Forward- oder External-Deklaration: 'TfrmMain.mnuFileCloseClick'
[Fehler] uFormMain.pas(43): Ungenügende Forward- oder External-Deklaration: 'TfrmMain.mnuEditCutClick'
[Fehler] uFormMain.pas(45): Ungenügende Forward- oder External-Deklaration: 'TfrmMain.mnuEditPasteClick'
[Fataler Fehler] ExtendableApp.dpr(10): Verwendete Unit 'uFormMain.pas' kann nicht compiliert werden
Das wären diese Zeilen:
Delphi-Quellcode:
procedure TfrmMain.mnuEditCopyClick(Sender: TObject);
begin
  if CurrentDocument <> nil then
    if CurrentDocument.mmoDocument.CopyToClipboard;
end;

procedure TfrmMain.mnuEditCutClick(Sender: TObject);
begin
  if CurrentDocument <> nil then
    if CurrentDocument.mmoDocument.CutToClipboard;
end;

procedure TfrmMain.mnuEditPasteClick(Sender: TObject);
begin
  if CurrentDocument <> nil then
    if CurrentDocument.mmoDocument.PasteFromClipboard;
end;
Jeweils die zweite if-Bedingung.

Delphi-Quellcode:
procedure TfrmMain.mnuFileCloseClick(Sender: TObject);
begin
  if CurrentDocument <> nil then
    if CurrentDocument.CloseDocument then
      ShowMessage('yes')
    else
      ShowMessage('no');
end;

procedure TfrmMain.mnuFileExitClick(Sender: TObject); // <- Zeile 112
begin
  Close;
end;
Michael
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 3 von 4     123 4      


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 12:19 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