Menüeinträge aus DLL Laden (Plugin)
-
Hallo alle zusammen,
ich hätte da mal wieder eine Frage an euch.
Ich habe ein Programm , das aus einem Hauptprogramm (die .exe Datei) und mehrere DLLs besteht. In dem Hauptprogramm kann der Benutzer diverse
zusätzliche kleine Dialoge über das Mainmenü öffnen und dort dann diverse Einstellungen tätigen oder sonstige Daten eingeben. Diese Dialoge
befinden sich, gruppiert nach übergeordneter Aufgabe, in entsprechenden DLLs. Wenn ich jetzt in einer DLL einen neuen Dialog hinzufüge, dann brauche
ich ja auch wieder einen neuen Eintrag im Mainmenü des Hauptprogrammes, mit dem entsprechenden Event in welchem die passende DLL z.B. dynamisch
geladen und die entsprechende Funktion zum Öffnen des Dialogs aus der DLL aufgerufen wird.Nun möchte ich diesen Vorgang so nicht haben, sondern vielmehr das das Hauptprogramm nach zugehörenden DLLs sucht, und z.B. über eine ID-Funktion
aus der DLL herausliest welche Dialoge enthalten sind und daraus dann dynamisch das Mainmenü aufbauet (also das Hauptprogramm hat dann eben
nur soviel Menüeinträge wie auch wirklich Funktionen verfügbar sind).
Gibt es dafür allgemeine Grundlegende Vorgehensweisen, auch im Hinblick auf die zu übergebenden Daten bezüglich der enthaltenen Dialoge? Also,
wäre es möglich eine 'Struct' zu übergeben, oder doch lieber eine TStringList. Und sollte das Hauptprogramm bei jedem Start von neuem komplett
nach 'Dialog'-Dlls suchen, oder die bereits gefundenen aus z.B. einer XML-Datei lesen und einbinden?Wäre schön wenn mir da jemand weiterhelfen könnte und ein paar Handreichungen für mich hätte.
Grüße Netzschleicher
-
Wenn du dynamische RTL und Laufzeitpackages verwendest, kannst du grundsätzlich alles hin- und herreichen.
Wie du deine Plug-ins suchst, ist wirklich Geschmackssache. Manche Programme fassen alles als Plug-in auf, was die Endung *.dll hat und in einem bestimmten Ordner im Anwendungsverzeichnis herumliegt. Manche verwenden die Registry oder eine XML-Datei als Plug-in-Registrierungsverzeichnis. (Für sowas finde ich die Registry übrigens ideal.) Kommt grundsätzlich drauf an, wie offen du die Plug-in-Schnittstelle halten willst.
Das dynamische Laden von DLLs würde ich irgendwie so machen:
- Für jeden Dialog implementiert die DLL folgendes Interface:
/*abstract*/ class IDialog { public: virtual String getMenuEntry (void) = 0; virtual void showDialog (Data* data) = 0; };
Die große Frage ist natürlich, wie die Struktur "Data" aussehen kann, so daß sie für alle denkbaren Dialoge genügend Informationen bereitstellt. Das ist die wesentliche Herausforderung für dich.
- Die Anwendung wiederum implementiert dieses Interface:
/*abstract*/ class IApplication { public: // die DLL benutzt diese Funktionen, um ihre Plug-ins zu registrieren virtual void registerDialog (IDialog* dialog) = 0; virtual void unregisterDialog (IDialog* dialog) = 0; // deine Main-Form benutzt diese Funktionen, um die Dialoge ins Menu einzutragen virtual unsigned getDialogCount (void) = 0; virtual IDialog* getDialog (unsigned index) = 0; };
- Jede DLL, die als Plug-in durchgehen soll, exportiert die folgende Funktion:
extern "C" __declspec (dllexport) MySpecialPluginRegistationFunction (IApplication* app);
Beim Start geht die Anwendung die Liste der Plug-ins durch, lädt jede der DLLs mit LoadLibrary(), findet obige Funktion mit GetProcAddress() und ruft sie auf. Die Funktion in der DLL benutzt dann die übergebene Schnittstelle, um ihre Dialoge zu registrieren.
Eine typische Implementierung auf der DLL-Seite:
class ColorDialog : public IDialog { public: virtual String getMenuEntry (void) { return "Change color"; } virtual void showDialog (Data* data) { std::auto_ptr<TColorDialogForm> form (new TColorDialogForm (0)); form->FromData (data); // Formular mit Daten füllen if (form->ShowModal () == mrOk) // mit einem Klick auf "Abbrechen" oder "X" passiert // nichts (setzt voraus, daß du TButton::ModalResult // richtig setzt) form->ToData (data); // geänderte Daten vom Formular lesen } }; struct DialogRegistrationHelper { private: IApplication* app; std::auto_ptr<IDialog> dialog; void unregisterDialog (void) { if (app && dialog.get ()) { app->unregisterDialog (dialog.get ()); dialog.reset (); } } public: DialogRegistrationHelper (void) : app (0) { } ~DialogRegistrationHelper (void) { unregisterDialog (); } void registerDialog (IApplication* newApp, IDialog* newDialog) { std::auto_ptr<IDialog> newDialogGuard (newDialog); // Exception-Sicherheit unregisterDialog (); app = newApp; app->registerDialog (newDialog); // erst registrieren... dialog.reset (newDialog.release ()); // ...dann Besitztum des Dialogs übernehmen } }; // globale Variablen werden vor dem Entladen der DLL destruiert - wird die DLL entladen, // macht sie also die Registrierung der Dialoge rückgängig, so daß du zur Laufzeit // Plug-ins laden und entladen kannst static DialogRegistrationHelper colorDialogHelper, shapeDialogHelper, ...; extern "C" __declspec (dllexport) MySpecialPluginRegistationFunction (IApplication* app) { colorDialogHelper.registerDialog (app, new ColorDialog); shapeDialogHelper.registerDialog (app, ...); ... }
Edit: Zwei Anmerkungen noch:
- Das Speichermanagement ist, wie du siehst, etwas kompliziert. Das kannst du vereinfachen, indem du einen intrinsischen (!) Smart-Pointer wie DelphiInterface<> sowie COM-Referenzzählung verwendest (d.h. deine Interfaces von IUnknown erben). Dazu mehr, wenn's dich interessiert.
- Die oben beschriebene Technik ist gemeinhin unter dem Buzzword "Dependency Injection" bekannt. Es gibt Dependency-Injection-Frameworks, deren wesentlicher Zweck darin besteht, Probleme wie deines mit möglichst wenig Aufwand und möglichst viel Abstraktion lösbar zu machen. (Du wirst sicher bemerken, daß einige der Dinge in meinem Beispiel weiter abstrahiert werden können. Wenn ich z.B. neben Dialogen auch Dateiformat-Exporter per Plug-in registrieren will, müßte ich IApplication um vier weitere Methoden erweitern, die genau dasselbe tun, nur für ein anderes Interface.)
-
Das ging ja schnell jetzt, hätte ich nicht damit gerechnet.
In der Tat hast Du Recht. Die Überlegung wie die Struktur auszusehen hat aus welcher das Hauptprogramm die nötigen Informationen herauslesen
kann, habe ich auch noch nicht so wirklich gefunden. Wäre das mit einer TStringList möglich, die dann eben entsprechend der Anzahl von Einträgen
ja mitwächst?Die Suche der zu der Anwendung gehörenden DLLs könnte ja über bestimmte Kürzel im Namen der DLL erfolgen, oder wie Du schon angedeutet hast,
diese DLLs eben in einem Unterverzeichnis der Anwendung ablegen und nur dort Suchen. Die auf der DLL Seite von Dir vorgeschlagene Funktion
zur Abfrage der evtl. enthaltenen Dialoge hatte ich so ähnlich auch schon im Sinn.Für die Implementierung auf der Hauptprogrammseite habe ich noch keine fixe Idee. Aber Dein Codegrüst ist eine gute Hilfe. Werd mich an das ganze
in einem kleinen Testprojekt herantasten. Auf jeden Fall wird das sicher wieder ein Lehrreiches Wochenende.Grüße Netzschleicher
-
Netzschleicher schrieb:
Das ging ja schnell jetzt, hätte ich nicht damit gerechnet.
Ich hatte gerade Zeit
Netzschleicher schrieb:
In der Tat hast Du Recht. Die Überlegung wie die Struktur auszusehen hat aus welcher das Hauptprogramm die nötigen Informationen herauslesen
kann, habe ich auch noch nicht so wirklich gefunden. Wäre das mit einer TStringList möglich, die dann eben entsprechend der Anzahl von Einträgen
ja mitwächst?Möglich sicher, aber vermutlich unschön. Was hast du denn so für Dialoge? Beschreibe doch mal zwei oder drei so ausführlich, daß man sich vorstellen kann, was für Daten sie benötigen.
-
Die Dialoge sind eigentlich nur einfache Form, in denen der Benutzer z.B. für die Funktion des Programms die Stammdaten eingeben kann.
Diese werden von der Form über die in der Datenmodul-DLL enthaltenen ADO-Komponenten direkt in die Datenbank geschrieben werden.
Die Dialoge selbst tauschen direkt keine Daten mit dem Hauptprogramm aus. Die zu exportierenden Funktionen der 'Plugin-DLLs' bestehen
somit nur aus den Aufrufen zum Darstellen der jeweiligen Form.z.B.
//--------------------------------------------------------------------------- // Funktion zum Erzeugen des About-Fensters //--------------------------------------------------------------------------- void ShowTPOAbout(TComponent* Owner) { frmTPOabout = new TfrmTPOabout(Owner); frmTPOabout->ShowModal(); delete frmTPOabout; frmTPOabout = NULL; } ... //--------------------------------------------------------------------------- // Funktion zum Erzeugen des Options-Dialogs. //--------------------------------------------------------------------------- void ShowTPOOptions(TComponent* Owner) { frmTPOoptions = new TfrmTPOoptions(Owner); frmTPOoptions->ShowModal(); delete frmTPOoptions; frmTPOoptions = NULL; }
-
Netzschleicher schrieb:
Die Dialoge selbst tauschen direkt keine Daten mit dem Hauptprogramm aus.
D.h., sie greifen selbständig auf den Datenbestand zu, der irgendwie als Service zugreifbar ist wie eben eine gemeinsame Datenbank? Dann ist es doch sehr einfach; die "Data"-Struktur ist ein Platzhalter, falls du später doch Daten übergeben willst, und bleibt bis auf weiteres einfach leer.
-
Gut, dann habe ich ja jetzt mal den Rahmen um an dem Thema zu arbeiten.
Vielen Dank schonmal. Obwohl ich sicherlich nochmal die eine oder andere Frage dazu habe dann.