Plugin-Liste speichern und wieder laden (quasi Reflection in C++?)
-
Hi,
möchte gerne eine Anordnung von vom Anwender ausgewählten Plugins meines Programmes speichern und wieder laden können. Die ausgewählten Plugins halte ich in einem std::vector<Plugin>. Jeder Plugin-Typ kann beliebig oft vorhanden sein.
Zur Zeit ist das ganze noch recht überschaubar, da ich "nur" ca. 60 verschiedene Plugin-Typen habe, aber es sollen ja mal noch viel mehr werden.
Jedes Plugin besitzt einen eindeutigen Typ-Namen und leitet sich von der Basisklasse "Plugin" ab.
Zur Vereinfachung nehmen wir aber mal an es gäbe nur diese 3 Typen:
- Rechteck
- Kreis
- DreieckJetzt enthält mein Vector also beispielsweise:
Rechteck, Rechteck, Rechteck, Kreis, Rechteck, Dreieck, Dreieck, KreisZum speichern durchlaufe ich nun diesen Vector und speichere den Typ-Namen sowie seine Parameter (z.B. Position und Größe).
Beim laden prüfe ich zunächst um welchen Typ es sich handelt und erstelle eine Instanz der jeweiligen Klasse. Das ganze ist einfach eine switch/case Liste, in dem ich aufgrund des zuerst geladenen Namens entscheide welche Instanz ich erstellen muss.
In .NET kann ich hier die zugehörige Klasse recht einfach via Reflection über den Klassennamen ermitteln, aber in Native C++ gibt`s sowas ja nicht und darum eben diese switch/case Liste.
Wie kann ich das annähernd so elegant bzw. so flexibel wie unter .NET lösen? Ich möchte später mal in der Lage sein ein neues Plugin einzubauen ohne beachten zu müssen diese switch/case Anweisung zu erweitern. Ich meine, VST-Hosts machen das ja bei ihren VST-Plugins auch anonym, schließlich kann es sich ja auch um Plugins von Drittanbietern handeln die völlig unbekannt sind, sich aber allesamt von einer Basisklasse ableiten.
Habt Ihr hier Tipps?
Viele Grüße
Goa
-
.
-
ueblicherweise kommen plugins als shared library und bieten eine bestimmte Funktion an, die das Hauptfunktion aufruft. Dort wird dann eine plugin-klasse registriert. Dein Beispiel klingt sehr nach abstract factory.
Beachte aber, dass in Windows aller Speicher dort geloescht werden muss, wo er erzeugt wurde, weil manchmal verschiedene Runtimes fuer die einzelnen Programme gelinkt sind. Das kann man z.B. ueber eine virtuelle Methode, die delete auf this aufruft, machen.Du kannst dir mal Irrlicht anschauen. Dort ist das genauso geloest, dass man mit Plugins neue Objekttypen in eine 3d-Szene integrieren kann.
-
Doch, das Schlüsselwort "virtual" kenne ich und nutze ich natürlich sehr viel in meiner Basisklasse "Plugin". Ich komme jetzt nur nicht drauf wie mir das hier weiterhelfen kann?
EDIT:
Auch der Typname meiner Plugins wird ja über eine Pure-Virtual Function zurückgeliefert.EDIT2:
Irrlicht ist nen guter Tipp, dass kenne ich. Gucke da gleich mal in den Source.Danke + LG
Goa
-
Wie wäre es mit sowas ähnlichem:
#include <memory> #include <vector> #include <string> #include <iostream> #include <unordered_map> class base { public: virtual ~base() = default; virtual void helau() = 0; }; class d1 : public base { public: void helau() override { std::cout << "D1 Allaaaf!\n"; } }; class d2 : public base { public: void helau() override { std::cout << "D2 Allaaaf!\n"; } }; template <typename T> std::unique_ptr<base> factory() { return std::make_unique<T>(); } int main() { using namespace std; vector<unique_ptr<base> (*)()> factories; factories.push_back(factory<d1>); factories.push_back(factory<d2>); size_t n; cin >> n; if(n < factories.size()) { unique_ptr<base> foo = factories[n](); foo->helau(); } unordered_map<string, unique_ptr<base> (*)()> same_factores; same_factores["dummkopf"] = factory<d1>; same_factores["sozialdemokrat"] = factory<d2>; string str; cin >> str; auto iter = same_factores.find(str); if(iter != end(same_factores)) { unique_ptr<base> bar = (*iter->second)(); bar->helau(); } }Plattformunabhängig und ohne komisches DLL und
delete this-Gedöhns.
-
GoaZwerg schrieb:
In .NET kann ich hier die zugehörige Klasse recht einfach via Reflection über den Klassennamen ermitteln, aber in Native C++ gibt`s sowas ja nicht und darum eben diese switch/case Liste.
Wie kann ich das annähernd so elegant bzw. so flexibel wie unter .NET lösen? Ich möchte später mal in der Lage sein ein neues Plugin einzubauen ohne beachten zu müssen diese switch/case Anweisung zu erweitern.
(...)
Habt Ihr hier Tipps?So in der Art...?
class PluginInstance; // Siehe unten class SerializedPluginParameters { ... }; // Kannst du definieren wie du magst, kann z.B. einfach ein std::string sein oder ne map<string, string> class PluginType // Könnte man auch PluginFactory oder so nennen { public: virtual std::string GetTypeName() const = 0; virtual PluginInstance* CreateInstance(SerializedPluginParameters const& params) const = 0; }; class PluginInstance { public: virtual ~PluginInstance(); virtual PluginType& GetType() const = 0; virtual SerializedPluginParameters GetParameters() const = 0; };Irgendwo müssen dann noch bei der Initialisierung des Programms alle
PluginTypeInstanzen registriert werden, damit man sie anhand des Namens (GetTypeName) nachschlagen kann.Das können die
PluginType-Instanzen z.B. selbst erledigen, indem sie in ihrem Ctor als letztes einPluginTypeRegistry::Register(this)o.ä. aufrufen.Doof dabei ist bloss dass man irgendwie dafür sorgen muss dass die
PluginType-Instanzen auch wirklich konstruiert werden. Was angesichts des C++ Standards und der Regeln für die Initialisierung von statischen/globalen Objekten nicht ganz trivial ist (IIRC gelten diese Regeln auch für static data members - falls nicht bitte ich um Korrektur).Alternativ kann man natürlich eine Funktion machen in der man manuell alle
PluginType-Instanzen per Hand registriert. Das muss man auch immer noch "richtig" machen, was dann aber eher trivial ist. (Da es schon spät ist und mein Beitrag sowieso schon lange genug ist, gehe ich hier nicht weiter ins Detail, bei Interesse einfach selbst googeln oder nachfragen.)Ich meine, VST-Hosts machen das ja bei ihren VST-Plugins auch anonym, schließlich kann es sich ja auch um Plugins von Drittanbietern handeln die völlig unbekannt sind, sich aber allesamt von einer Basisklasse ableiten.
Bei Systemen wie VST-Plugins sind die Plugins meist DLLs die in einem bestimmten Verzeichnis liegen - oder deren Pfad explizit irgendwo in einer "Liste" (Config-File, ...) erfasst ist.
Das vereinfacht die Sache sogar noch ein wenig, nämlich dadurch dass man trivial sicherstellen kann dass diePluginType-Instanzen auch wirklich erzeugt und eingetragen werden.Um aus dem oben Skizzierten so etwas zu machen, würde es z.B. schon reichen folgende Funktion aus jeder Plugin-DLL zu exportieren:
PluginType* GetPluginType(); // Bzw. evtl mit "size_t n" Parameter, falls du mehrere Plugins // in der selben DLL ermöglichen willst -- was sehr üblich wäreDer Initialisierungs-Code deines Programms lädt dann alle DLLs, holt sich jeweils per
GetProcAddressnen Zeiger auf dieGetPluginTypeFunktion, und ruft diese auf um diePluginType-Instanz(en) zu bekommen. Und registriert sie irgendwo.Ansonsten guck' dir auch einfach mal das Interface für Winamp 2.x DSP-Plugins an. Das bietet auch so eine Funktionalität. Ist zwar ne reine C-Schnittstelle, aber das ändert eigentlich nichts wesentliches.