benutzen von RTTI in verschiedenen Programmmodulen (Plugins)



  • Moin!

    Also es geht sich um Folgendes:

    Ich bastle gerade an einem plugin-System, das in etwa so funktioniert, dass ein Interface bereitgestellt wird mittels einer abstakten Klasse.
    Ein plugin (welches zur Laufzeit zum Hauptprogramm gelinkt wird) soll mehrere verschiedene Interfaces implementieren können.

    Ich hab mir gedacht, das Hauptprogramm ruft eine Funktion (die in jeden plugin den selben Namen trägt) auf, welche ein Factory-Objekt zurückgibt.
    Nun muss das Hauptprogramm natürlich wissen welches interface das plugin implementieren will. Deshalb dachte ich ich liefere gleich noch ein "std::type_info" mit.
    Nur weis ich nicht ob RTTI noch korrekt arbeitet wenn das plugin mit einem anderen compiler als das Hauptprogramm übersetzt wurde...

    Noch etwas code um das Grundprinzip zu verdeutlichen:

    Die Factory:

    class IObjectFactory
    {
    public:
    	virtual const std::type_info& getInterfaceType() const = 0;
    	virtual void*                 construct       () const = 0;
    };
    

    Das Interface, dass das plugin implementieren soll:

    class IAnyPluginInterface
    {
    public:
    	virtual void doSomePluginStuff() = 0;
    };
    

    Das plugin:

    class APluginClass: public IAnyPluginInterface
    {
    	virtual void doSomePluginStuff()
    	{
    		std::cout << "I'm doing some plugin stuff\n";
    	}
    }
    
    class ConcreteObjectFactory: public IObjectFactory
    {
    	virtual const std::type_info& getInterfaceType() const
    	{
    		return typeid(IAnyPluginInterface);
    	}
    
    	virtual void* construct() const
    	{
    		return new APluginClass;
    	}
    };
    
    extern "C" IObjectFactory* plugin_main()
    {
    	return new ConcreteObjectFactory;
    }
    

    Das Hauptprogramm:

    int main()
    {
    	typedef IObjectFactory* (*PluginMainFunction)();
    
    	SharedObject so("plugin.so");
    	PluginMainFunction getObjectFactory = static_cast<PluginMainFunction>(so.getFunction("plugin_main"));
    
    	IObjectFactory* factory = getObjectFactory();
    
    	if(factory->getInterfaceType() == typeid(IAnyPluginInterface)) //<-- KRITISCHE STELLE
    	{
    		IAnyPluginInterface* iFace = static_cast<IAnyPluginInterface*>(factory->construct());
    		iFace->doSomePluginStuff();
    	}
    }
    

    Ich befürchte nämlich, dass "IAnyPluginInterface" im plugin ein anderen type_info hat als im Hauptprogramm...

    Wäre nett wenn mir jemand sagen könnte ob Das so funktioniere.
    --> Und für Verbesserungsvorschläge, was das ganze Konzept angeht währe ich auch sehr dankbar <--

    mfg
    plassy



  • Ich bin ja nur besorgt weil ich oft gelesen habe, dass der compiler mit dem RTTI zeug intern machen kann wass er will (z.B. std::type_info::name(...) gibt auch einen Namen eines Typs zurück den der compiler wählt wie er lustig ist)

    Wie kann dann sichergestellt werden, dass ein und der selbe Typ der mit verschiedenen compilern übersetzt wurde auch die gleiche Typinformation besitzt??

    mfg
    Plassy



  • Ich versteh jetzt nicht soviel von RTTI und Laufzeitlinken.

    Kannst du nicht den Interfaces eine ID mitgeben, oder ein enum, und im Hauptprogramm eine Liste?



  • Das mit der ID hab ich mir auch schon überlegt, nur würde das die ganze flexibilität zerstören.
    Wenn das plugin-Framework schon vorher weis welche Objekte es erzeugen kann, muss man immer wenn man ein neues interface hinzufügt das plugin-Framewoirk erweitern und das ist inakzeptabel.

    Natürlich könnte ich auch die Typüberprüfun weglassen und einfach trauf los casten...

    Das beste wird wohl sein das factory-Objekt gibt noch einen string zurück mit dem Namen des interfaces wofür es ein Objekt erzeugt.

    class ConcreteObjectFactory: public IObjectFactory
    {
        virtual const std::type_info& getInterfaceType() const
        {
            return typeid(IAnyPluginInterface);
        }
    
        virtual void* construct() const
        {
            return new APluginClass;
        }
    
        virtual string getInterfaceName() const
        {
            return "IAnyPluginInterface"
        }
    };
    

    Dann baue ich noch ne Funktion, die alle factories durchsucht und die richtige auswählt...

    template<class TInterface>
    TInterface* getObject(string interfaceName)
    {
        for factory in factoryList
        {
            if(factory->getInterfaceName() == interfaceName)
                return static_cast<TInterface*>(factory->construct());
        }
    }
    
    int main()
    {
        IAnyPluginInterface* iFace = getObject<IAnyPluginInterface>("IAnyPluginInterface");
    }
    

    Ist zwar schön nur was mich stört ist, dass ich wenn ich ein Objekt erstellen will alle factories durchsuchen muss bis ich die Richtige finde...
    Ich hätte lieber ne möglichkeit mit nen Index auf das passende factory-Objekt zuzugreifen... es will aber einfach keine anständige Lösung in meinen Schädel 😡

    mfg
    plassy



  • Egal ob mit RTTI oder sonstwas an einer Stelle legst du fest wie viele Schnittstellen es gibt und du musst beim Laden rausfinden welche Schnittstelle(n) das Plug-In verwendet.
    Von daher ist die ID-Lösung nicht umständlicher, wenn du nicht gerade nen Design benutzt bei dem man an 10Stellen was ändern muss, wenn eine neue Schnittstelle dazu kommt.

    Ich würde alle Schnittstellen in einer Hierachie zusammenfassen, so ca.

    typedef unsigned ID; //vielleicht willst du ja lieber ne GUID als ID oder so
    class BasePluginInterface
    {
    virtual ID getID() const = 0;
    };
    
    class PluginInterface1 : public BasePluginInterface
    {
    
    virtual ID getID() const
    { return ID(1); }
    };
    

    Jetzt kannst du in deinem Hauptprogramm alle IDs für die Interfaces festlegen und dann noch in ne Tabelle eintragen, so musst du beim hinzufügen nur noch den Eintrag in die Tabelle erledigen und das Interface selbst erstellen.

    Beim Laden des Plug-Ins kann dieses dir ja ein Info-Objekt zurückgeben das eine Tabelle mit allen IDs und ner dazugehörigen Factory-Fuktion zurückgibt.

    Danach kannst du die Interfaces erzeugen und weißt durch die ID um welches es sich handelt.

    Indem Moment wo du jedoch grundlegend verschiedene Schnittstellen hast und die unterscheiden musst wirst du aber immer unflexibler was das Design angeht. Also schöner wird es dadurch auch nicht, würde es auch nicht mit RTTI werden.
    Um den "Identifikations-UndMachWasBestimmtes-Block" kommst du nicht rum und den musst du jedes mal anpassen wenn was dazu kommt.



  • hmmm... auf jeden Fall kann das plugin, welches das Interface implementiert nicht die ID liefern...

    Was ist wenn mehrere Leute verschiedene interfaces implementieren die die gleichen IDs liefern?? Die Typüberprüfung durch die IDs ist gültig, es wird aber ein falsches interface implementiert... das gibt mit sicherheit n crash.

    Außerdem, woher soll ein Pluginentwickler wissen welche IDs schon vergeben sind??
    Dann müsste das interface schon selbst die ID mitliefern...

    Hast du das gemeint... oder raff ich da was nicht??

    Naja... ich muss jetzt leider weg... ich kann auch erst wieder am kommenden Freitag posten 😞
    Vielleicht hab ich bis dahin ja na passable Lösung... oder ihr vielleicht :p

    mfg
    Plassy



  • Plassy schrieb:

    Was ist wenn mehrere Leute verschiedene interfaces implementieren die die gleichen IDs liefern?? Die Typüberprüfung durch die IDs ist gültig, es wird aber ein falsches interface implementiert... das gibt mit sicherheit n crash.

    Außerdem, woher soll ein Pluginentwickler wissen welche IDs schon vergeben sind??

    Das muss halt festgelegt und dokumentiert werden.



  • Nein, das meinte ich nicht.

    Die Interfaces, welche von den Entwickler der Plug-Ins dann implementiert werden, bekommen eine ID und die bestimmst du. Diese ID ist nur eine Möglichkeit um den Interface-Typ zur Laufzeit feststellen zu können.
    So wie mein Beispiel oben ist, könnte der Plugin-Entwickler die ID-Funktion überschreiben und einen anderen Wert zurückgeben, wenn du das unterbinden möchtest, dann solltest du die Funktion private machen und deine Anwendungsklasse(n) zu friend(s) machen.



  • Ich verstehe das Problem nicht so ganz. Wenn du den gleichen Compiler für alle Plugins voraussetzen kannst (und das musst du fast, sonst geht eh so gut wie nixmehr bei C++ DLLs), dann solltest du auch einfach den "rawname" aus der type_info verwenden können.

    Die IMHO viel einfachere Möglichkeit wäre allerdings jedem Interface eine GUID zu verpassen. Für Interfaces die du selbst definierst legst du die GUID einfach fest, und für Interfaces die andere Plugins selbst definieren (um z.B. mit wieder anderen Plugins zu kommunizieren) legen die sich eben selbst eine GUID fest. Da GUIDs ausreichend lange sind um nicht zu kollidieren sollte das doch vollkommen ausreichen. Im Prinzip ist das genau die Technik die auch COM verwendet, und da funktioniert das auch problemlos.


Anmelden zum Antworten