Autor |
Beitrag |
Henry
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: Fr 10.10.03 15:20
Hallo,
ich habe begonnen in eines meiner Programme eine Pluginschnitstelle einzubauen. Nun habe ich in meinem Programmverzeichnis einen Ordner \Plugin in den die Plugins (DLLs) vom User hineinkopiert werden sollen. Soweit ist es auch kein Problem, aber ich möchte die Möglichkeit schaffen das sich jeder User nur die Plugins integrieren kann die er gerne nutzen möchte. Im HTML-Editor Phase5 wird ein Menüeintrag eingefügt wenn eine neue DLL in den Pluginordner kopiert wurde.
So ungefähr würde ich das auch gerne lösen, denn sonst müste ich ja zu jedem neuen Plugin eine neue Version der Software erstellen damit der eintrag vorhanden ist. Ich weiß nur leider nicht wie ich es machen kann das immer wenn eine neue DLL im Ordner hinzugefügt wird ein neuer Eintrag in meinem Menü auftaucht und dann auch noch die Funktion in der entsprechenden DLL aufruft.
Kann mir da jemand helfen?
Danke im voraus
Moderiert von Tino: Überflüssige Absätze entfernt.
_________________ mfg Henry
|
|
bIce
      
Beiträge: 55
|
Verfasst: Fr 10.10.03 16:50
Ich vestehe nicht ganz wo das Problem ist. Alle PlugIn-Dlls müssen die gleiche Schnittstelle haben.
Bsp:
Delphi-Quelltext 1: 2: 3: 4:
| function Init: boolean; stdcall; function GetName(var name: string) : Boolean; stdcall; function GetIcon(var icon: TIcon) : Boolean; stdcall; function OnCall(event: Integer): Boolean; stdcall; |
Zurückgegeben wird ob die Funktion erfolgreich ausgeführt wurde oder nicht.
Dann in der Hauptanwendung musst Du beim Laden, alle Dlls laden. Den Namen und das Icon abholen und daraus ein Menü erstellen. Beim Schließen alle Dlls wieder freigeben.
Du musst Noch z.B. ein Array mit HMODULE erstellen, wo die Handles zu den Dlls gespeichert werden. und den Index an TMenuItem.Tag zuweisen, dann eine Ereignisbehandlungsroutine für OnClick erstellen, wo mit Hilfe des TMenuItem.Tag und dem Array auf OnCall zugegriffen wird. Wie man Dlls lädt/freigibt, und funktionen benutzt muss ich wohl nicht erklären. Wenn du es nicht weißt dann schaue in der Hilfe nach LoadLibrary/FreeLibrary/GetProcAddress
Moderiert von Tino: Delphi-Tags hinzugefügt.
_________________ Gott mag anscheinend einfache Leute, denn er hat viele von ihnen erschaffen.
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: Fr 10.10.03 19:16
Danke Dir erstmal für die Antwort.
Das Die schnittstelle bei allen DLLs gleich sein muß ist klar.
Mein Problem besteht darin, das Menü zu erstellen.
Wie kann ich denn eine unbekannte Anzahl an DLLs laden von denen ich den Dateinamen nicht kenne?
Dann ist mir auch nicht ganz klar wie ich mit dem die Menüeinträge erstelle und das OnClick- ereignis für die einzelnen Einträge definiere.
Für die ersten Versuche habe ich mit folgender Funktion die DLL dynamisch eingebunden
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| Procedure DLLAufruf(Dateiname, Funktionsname : String); VAR Plugin : TPlugin1; FunctPtr : TFarProc; DLLHandle : THandle;
Begin DLLHandle := LoadLibrary(PChar(Dateiname)); FunctPtr := GetProcAddress(DLLHandle, PChar(Funktionsname)); if FunctPtr <> nil then Begin @Plugin := FunctPtr; Plugin; FunctPtr := nil; End Else Showmessage('Plugin nicht gefunden!'); FreeLibrary(DLLHandle); End; |
_________________ mfg Henry
|
|
bIce
      
Beiträge: 55
|
Verfasst: Fr 10.10.03 23:04
Um an den Dateinamen zu kommen musst Du wissen, wo die Dll gespeichert ist. Dann kannst Du FindFirst/FindNext/FindClose-Verwenden um die Namen zu erhalten. Um sich ganz sicher zu sein, dass es sich dabei um ein PlugIn handelt musst Du die dll laden, und versuchen alle notwendigen Funktionen abzufragen. Wenn es ein PlugIn ist dann wird es im Array gespeichert.
Der Übersichtslichkeitshalber habe ich mich nur auf das Notwendigste beschränkt. Ich habe es nicht auf irgendwelche Fehler geprüft, falls etwas nicht funktionieren sollte, dann lass es mich wissen.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108:
| procedure GetPlugInNames(path: String; const items : TStringList); var sr : TSearchRec; begin path := IncludeTrailingBackslash(path) if FindFirst(path + '*.dll', faAnyFile, sr) = 0 then begin repeat if IsPlugIn(path + sr.Name) then items.Add(path +sr.Name); until FindNext(sr) <> 0; FindClose(sr); end; end;
function IsPlugIn(path: String): Boolean; var h : HMODULE; name: TGetNameFunc; evt : TOnCallFunc; begin Result := false; h := LoadLibrary(PChar(path)); if h = nil then exit; name := TGetNameFunc(GetProcAddress(h, 'GetName')); if name = nil then exit; evt := TOnCallFunc(GetProcAdress(h, 'OnCall')); if evt = nil then exit; Result := true; end;
TForm1 = class(TForm) ... private plugIns_ : array[0..1024] of HMODULE; count_ : Integer; class procedure OnPlugInMenuClick(sender : TObject); procedure LoadAllPlugIns; procedure FreeAllPlugIns; end;
class procedure TForm1.OnPlugInMenuClick(sender : TObject); var evt : TOnCallFunc; begin if sender is TMenuItem then with sender as TMenuItem do if (Tag >= 0) and (Tag < count_) then begin evt := TOnCallFunc(GetProcAddress(plugIns_[Tag], 'OnCall')); if evt <> nil then evt; end; end; end;
procedure TForm1.LoadAllPlugIns; var files : TStringList; i : Integer; nameF: TGetNameFunc; name : String; menu : TMenuItem; begin count_ := 0; files := TStringList.Create; try GetPlugInNames(ExtractFilePath(Application.Name) + 'PlugIns', files); for i:=0 to files.Count - 1 do begin plugIns_[count_] := LoadLibrary(files.Strings[i]);
nameF:= TInitFunc(GetProcAdress(h, 'Init')); if not initF(name) then FreeLibrary(plugIns_[count_]) else begin menu := TMenuItem.Create(mnuHauptEintrag); menu.Caption := name; menu.OnClick := OnPlugInMenuClick; menu.Tag := count_; mnuHauptEintrag.Items.Add(menu); end; Inc(count_); end; finally files.Free; end; end;
procedure TForm1.FreeAllPlugIns; var i : Integer; begin for i := 0 to count_ - 1 do FreeLibrary(plugIns_[i]); count_ := 0; end; |
_________________ Gott mag anscheinend einfache Leute, denn er hat viele von ihnen erschaffen.
Zuletzt bearbeitet von bIce am So 12.10.03 00:15, insgesamt 4-mal bearbeitet
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: Fr 10.10.03 23:11
Danke Dir.
Ich werde mich mal damit ein wenig beschäftigen.
Melde mich nochmal falls ich noch Probleme habe.
_________________ mfg Henry
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: Sa 11.10.03 23:51
Hallo,
Ich bin noch dabei mich mit den Code auseinanderzusetzen und mir eine eigene Unit zu entwickeln.
Dein Codebeispeil hilft mir übrigens sehr dabei.
Nun bin ich auf folgendes Problem gestoßen.
Die Procedure wo ich mir meine Menüeinträge für die Plugins erzeuge sieht an der Problemstelle so aus:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| For i := 0 to Items.Count - 1 do Begin DLLHandle := LoadLibrary(PChar(Items.Strings[i])); GetPluginName := TGetPluginName(GetProcAddress(DLLHandle,'PluginName'));
NewItem := TMenuItem.Create(nil); Newitem.Caption := GetPluginname; NewItem.Tag := Anzahl; NewItem.OnClick := PluginsOnClick; PluginMenu.Add(NewItem); Inc(anzahl); End; |
Wenn ich aber mein Projekt Compilieren möchte kommt an der Stelle
Delphi-Quelltext 1:
| NewItem.OnClick := PluginsOnClick; |
die Fehlermeldung:
[Fehler] Plugin.pas(49): Inkompatible Typen: Methodenzeiger und reguläre Prozedur
Kommentiere ich die Stelle aus, funktioniert alles wie gewünscht, nützt nur nichts einen Eintrag zu erzeugen der nichts aufruft:)
Wie kann ich denn im Code oben meine Procedure aufrufen die bei OnClick aufgerufen werden soll? Was mache ich falsch? Irgendwie muß es doch möglich sein, das ich eine Procedure oder Funcktion aufrufen kann, trotzdem ich den Menüeintrag erst zur Laufzeit erzeuge.
Könnte mir da nochmal jemand auf die Sprünge helfen?
Danke im voraus
_________________ mfg Henry
|
|
bIce
      
Beiträge: 55
|
Verfasst: So 12.10.03 00:01
Henry hat folgendes geschrieben: |
Wenn ich aber mein Projekt Compilieren möchte kommt an der Stelle
Delphi-Quelltext 1:
| NewItem.OnClick := PluginsOnClick; |
die Fehlermeldung:
[Fehler] Plugin.pas(49): Inkompatible Typen: Methodenzeiger und reguläre Prozedur
Kommentiere ich die Stelle aus, funktioniert alles wie gewünscht, nützt nur nichts einen Eintrag zu erzeugen der nichts aufruft:)
|
Schreibe vor der Definition/Deklaration von PluginsOnClick "class" (Ohne Anführungsstriche). Oder siehe den Quelltext von mir. Ich habe ihn angepasst.
_________________ Gott mag anscheinend einfache Leute, denn er hat viele von ihnen erschaffen.
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: So 12.10.03 00:06
Danke,
ich werde es mal versuchen
_________________ mfg Henry
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: So 12.10.03 00:35
Wenn ich einfach Class davor schreibe, dann kommt folgende Fehlermeldung
[Fehler] Plugin.pas(17): Deklaration von 'PluginsOnClick' unterscheidet sich von vorheriger Deklaration
Kann es daran liegen, das ich alle meine Funktionen die ich für meine Pluginbearbeitung verwenden möchte in einer eigenen Unit habe und nicht wie Du in der Unit des Formulars?
Denn ich möchte der Übersicht halber eine eigene Unit dafür haben, die alles enthält was ich brauche, dann kann ich es einfacher in andere Anwendungen übernehmen und muß nicht immer bei null anfangen.
Die Declaration sieht nun bei mir so aus
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| Interface
...
Class Procedure PluginsOnClick(Sender: TObject); Procedure LoadPlugins(Pfad: String; Pluginmenu:TMenuItem); |
und die Procedure so:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| Class Procedure PluginsOnClick(Sender: TObject); Begin If Sender is TMenuItem then Begin With Sender as TmenuItem do Begin End; End;
End; |
Wie gesagt alles in einer eigenen Unit, falls es einen Unterschied machen sollte.
_________________ mfg Henry
|
|
maximus
      
Beiträge: 896
Win XP, Suse 8.1
Delphi 4/7/8 alles prof
|
Verfasst: So 12.10.03 11:42
_________________ mfg.
mâximôv
|
|
bIce
      
Beiträge: 55
|
Verfasst: So 12.10.03 13:40
maximus hat folgendes geschrieben: | IMAO ist das mit dem 'class' quatsch...wüsste nicht was das zum problem beiträgt?
|
Stimmt, weiß garnicht mehr wie ich darauf gekomment bin ?
class macht es nur zu einer statischen Methode, was gar nicht erforderlich ist.
[code]
Zitat: |
Das bedeutet, dass du 'PluginsOnClick' als methode in einer klasse definieren musst:
Delphi-Quelltext 1:
| Procedure TDeineKlasse.PluginsOnClick(Sender: TObject); |
|
So stand es schon seit längerem da. Ist Henry vielleicht nicht aufgefallen.
_________________ Gott mag anscheinend einfache Leute, denn er hat viele von ihnen erschaffen.
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: So 12.10.03 21:50
Hallo ich bin es wieder
Ich bin mit eurer Hilfe schon ein großes Stück weiter gekommen.
Es ist aber ein weiteres Problem aufgetreten.
Ich möchte einfach eine Funktion in der PluginDLL aufrufen die mir ein True zurück giebt
wenn es ein Plugin ist.
Wenn es aber keine PluginDLL ist, dann existiert die Function ja nicht, deshalb bekomme ich eine Fehlermeldung. Giebt es eine Möglichkeit festzustellen ob die funktion existiert? So wie:
Delphi-Quelltext 1:
| FileExists('../datei.dll') |
So das das Programm nicht versucht die Funtion aufzurufen und dann abstürtzt.
So siet die betreffende Stelle bei mir aus. Nur wenn die Function in der DLL nicht existiert, dann giebt es einen Absturz des Programmes.
Delphi-Quelltext 1: 2:
| GetPluginName := TGetPluginName(GetProcAddress(Plugins_[Count_],'PluginName')); Init := TInit(GetProcAddress(Plugins_[Count_],'Init')); |
Vieleicht könntet Ihr mir da nochmal unter die Arme greifen
_________________ mfg Henry
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: Di 14.10.03 20:15
Das Problem ist gelöst, danke allen die mir geholfen haben.
EDIT: Meine Lösung die ich nun nutze ist:,
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| If GetProcAddress(Plugins_[Count_],'PluginName') = NIL then Begin End Else Begin End; |
Falls jemand anders auch soetwas sucht, und demjenigen somit auch geholfen ist.
_________________ mfg Henry
|
|
bIce
      
Beiträge: 55
|
Verfasst: Mi 15.10.03 18:31
Henry hat folgendes geschrieben: |
[...]
So das das Programm nicht versucht die Funtion aufzurufen und dann abstürtzt.
So siet die betreffende Stelle bei mir aus. Nur wenn die Function in der DLL nicht existiert, dann giebt es einen Absturz des Programmes.
[...]
|
Wozu willst Du eigentlich eine Spezielle Funktion, um zu überprüfen ob es ein PlugIn ist? Es wäre doch besser wenn Du einfach prüfst ob alle anderen Erforderliche Schnittstellen vorhanden sind. So wie in der oben geposteten Funktion IsPlugIn().
Wenn Du das Modell aber in mehreren Programmen , dann könntest Du eine ID für bestimmtes Programm verwenden, diese IDs könntest Du z.B. (wie in COM) mit CoCreateGuid() erzeugen.
_________________ Gott mag anscheinend einfache Leute, denn er hat viele von ihnen erschaffen.
|
|
Henry 
      
Beiträge: 619
WinXP Prof. SP3
D6 Prof.
|
Verfasst: Mi 15.10.03 19:27
bIce hat folgendes geschrieben: |
[...]
Wozu willst Du eigentlich eine Spezielle Funktion, um zu überprüfen ob es ein PlugIn ist? Es wäre doch besser wenn Du einfach prüfst ob alle anderen Erforderliche Schnittstellen vorhanden sind. So wie in der oben geposteten Funktion IsPlugIn().
[...]
|
Da hast Du recht, letzendlich werde ich es wohl so machen.
bIce hat folgendes geschrieben: |
Wenn Du das Modell aber in mehreren Programmen , dann könntest Du eine ID für bestimmtes Programm verwenden, diese IDs könntest Du z.B. (wie in COM) mit CoCreateGuid() erzeugen. |
Mit einer Guid habe ich mich auch noch nicht befasst, aber ist denn Die nicht bei jedem erzeugen anders?
Ich werde aber mal in der Hilfe nachschauen.
Da ich versuche meine Pluginschnittstelle erweiterbar zu gestallten, werde ich erstmal eine erste Version zum laufen bringen. Dann werde ich das ganze immer weiter ausbauen. Dazu sind natürlich alle Tips die ich bekommen kann nützlich für mich.
So lernt man ja auch dazu.
_________________ mfg Henry
|
|
Knulli
      
Beiträge: 116
Erhaltene Danke: 2
Win2k, Win7, Win10
D5, D2005, D2006, D2007, D10.4.2
|
Verfasst: Fr 23.03.07 14:54
Hallo Forum,
ist zwar schon ne Weile her, das hier diskutiert wurde, möchte trotzdem mal sehen, ob der Thread "noch lebt".
Frage: Könnte man in der (PlugIn-)DLL nicht auch eine einzige Funktion nur schreiben, die eine Referenz auf ein TMyPlugIn-Objekt zurückgibt? Alle eigentlich benötigten Funktionalitäten wären dann über die Member (Methoden und Eigenschaften) der Klasse "erreichbar". Stellt sich natürlich die Frage, was mit Methoden/Eigenschaften ist, die neu hinzukommen. Was steckt eigentlich alles an Informationen in so einem Parameter bzw. Rückgabewert vom Typ "TMyPlugIn"? Nur der Klassenname oder auch Informationen über alle Member? Hab mal irgendwann was über Late-Binding gehört, wo erst zur Laufzeit ermittelt wird, ob es ein bestimmtes Member in einer Klasse gibt. Wie kann ich sowas in Delphi erzwingen? Wenn das gehen würde, hätte man ja mit "TMyPlugIn" eine wunderbare, unendlich erweiterbare PlugIn-Schnittstelle.
Ich hoffe, nichts durcheinandergerbracht zu haben und hoffe auf Meinungen/Ideen/Kritik.
mfg Knulli
|
|
HelgeLange
      
Beiträge: 735
Erhaltene Danke: 6
Windows 7
Delphi7 - Delphi XE
|
Verfasst: Fr 23.03.07 18:18
rein technisch gesehen kannst du ein Standard-Object-Typ erzeugen und ihm mit Dispatch selbst-definierte Nachrichten schicken. Regiert das Object drauf (liefert ein bestimmtes Ergebnis zurück), wird diese Funktion von dem Object unterstützt. So kannst Du Dir eine Feature-Matrix für jedes geladene Plugin erstellen.
Das Problem wird sein, dass Du immer hinterher-hinkst. Gerade, wenn Du von anderen Programmierern Plugins akzeptieren willst. Solche Plugins haben einfach den Nachteil, sehr starr zu sein, da sie immer vom Code des Loaders abhängig sind und wie er die Schnittstelle interpretiert. Und je mehr du einbaust in die Schnittstelle (je mehr Funktionen), desto komplizierter wird das "Flöhe-Hüten"
Schau doch mal in den Thread "ERP Framework Components" (in "Kommerzielle Projekte"), dort habe ich ein wenig beschrieben, wie ich das gelöst habe, vielleicht hilft es Dir etwas ? Ansonsten frag halt hier nochmal 
_________________ "Ich bin bekannt für meine Ironie. Aber auf den Gedanken, im Hafen von New York eine Freiheitsstatue zu errichten, wäre selbst ich nicht gekommen." - George Bernhard Shaw
|
|