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
    - Dreieck

    Jetzt enthält mein Vector also beispielsweise:
    Rechteck, Rechteck, Rechteck, Kreis, Rechteck, Dreieck, Dreieck, Kreis

    Zum 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 PluginType Instanzen 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 ein PluginTypeRegistry::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 die PluginType -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äre
    

    Der Initialisierungs-Code deines Programms lädt dann alle DLLs, holt sich jeweils per GetProcAddress nen Zeiger auf die GetPluginType Funktion, und ruft diese auf um die PluginType -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.


Anmelden zum Antworten