Frage zu Plugins mit abstrakten Klassen



  • Hallo.
    Ist die Designidee hier gut und praktikabel, und vor allen Dingen erlaubt?

    class IExtension
    {
    public:
        virtual const std::string &GetName() const = 0;
        virtual const std::string &GetDescription() const = 0;
        virtual const std::string &GetVersion() const = 0;
        virtual const std::string &GetDate() const = 0;
        virtual const std::string &GetAuthor() const = 0;
        virtual const std::string &GetEMail() const = 0;
        virtual const std::string &GetURL() const = 0;
    };
    
    class IExtensionInstance : public IExtension
    {
    public:
        virtual bool OnLoad() = 0;
        virtual bool OnUnload() = 0;
        virtual bool OnRegisterNatives(ISystem *pSystem) = 0;
        virtual void OnExtensionLoad(IExtension *pExtension) = 0;
        virtual void OnExtensionUnload(IExtension *pExtension) = 0;
        virtual void OnAllExtensionsLoaded() = 0;
    };
    
    class IExtensionManager
    {
    public:
        virtual IExtension *Load(const std::string &strName) = 0;
        virtual bool Unload(IExtension *pExtension) = 0;
        virtual IExtension *Get(const std::string &strName) const = 0;
    };
    

    Meine Idee ist, wie man hoffentlich sieht, dass jede Extension auf andere Extensions zugreifen kann (momentan nur Daten abrufen), via IExtension . Jede Extension muss dazu natürlich alle Funktionen von IExtension implementieren. Außerdem werden noch einige Callbacks hinzugefügt, via IExtensionInstance . Funktioniert das nun auch so wie gewollt, dass ein Pluginautor gezwungen ist, alle Methoden von IExtension und IExtensionInterface zu implementieren? Und ist das legal nach dem Standard?

    Des weiteren wollte ich Fragen, was ihr von dem Konstrukt hier haltet.

    Edit: Noch eine Frage die mir nebenbei einfällt. Ich kenne Systeme, da gibt es solche oder ähnliche Defines zu jedem globalen Interface (so wie hier IExtensionManager ):

    #define IFACE_EXTENSIONMANAGER "ExtensionManager001"
    

    Ich glaube die Idee zu verstehen, nämlich dass bei einem Update der SDK die Version erhöht wird. Nur wo ist der Sinn? Dann müsste ich bei jedem Update auch die alten Versionen instanzieren, z.B. so:

    class IExtensionManager001
    {
    public:
        virtual IExtension *Load(const std::string &strName) = 0;
        virtual bool Unload(IExtension *pExtension) = 0;
        virtual IExtension *Get(const std::string &strName) const = 0;
    };
    
    class IExtensionManager //002
    {
    public:
        virtual IExtension *Load(const std::string &strName) = 0;
        virtual bool Unload(IExtension *pExtension) = 0;
        virtual IExtension *Get(const std::string &strName) const = 0;
        virtual void NeueFunktion() const = 0;
    };
    
    #define IFACE_EXTENSIONAMANGER "ExtensionManager002"
    

    Und dann in der Implementierung:

    pExtensionManager = new CExtensionManager;
    pExtensionManager001 = new CExtensionManager001;
    

    Spätestens hier hakt es doch. Ich kann doch nicht alle alten Versionen immer neu erstellen?!

    Danke!



  • ja und ja und .... pfuh

    erlaubt ist das alles
    und virtual pure funktionen müssen implementiert werden

    bloss gibt es einige dinge bei "C++ DLLs" zu beachten

    1. sobald STL klassen (wie z.b. std::string) im interface verwendet werden, muss zwingend bei allen beteiligten DLLs/SOs/EXEn die selbe standard library verwendet worden sein, und normalerweise auch der selbe compiler. wobei es exakt die selbe version sein muss. unterschiedliche revisionen können funktionieren, müssen aber nicht. wenns nur ein bugfix im compiler ist ist es normalerweise kein problem. sobald die STL geändert wurde kann man nurmehr beten. bzw. sich einfach nicht drauf einlassen und zwingend vorschreiben welche STL + compiler verwendet werden müssen.

    2. man muss sicherstellen dass speicher anfordern/freigeben sache zwischen den verschiedenen DLLs funktioniert. normalerweise ist das hinzubekommen, wenn man den selben compiler vorschreibt. unter windows/MSVC verwendet man dazu z.B. die DLL runtime, dann gibt es kein problem.

    3. je nach compiler/system sind noch weitere dinge zu beachten. z.B. halten bei MSVC in fall von DLLs gewisse C++ "versprechen" nicht mehr. wie z.B. dass eine funktion für alle die selbe adresse hat, egal wo man diese adresse ermittelt. oder dass statische membervariablen von templates nur 1x pro spezialisierung vorhanden sind.



  • zu deinem EDIT: zeich mal ein beispiel (also link auf ein konkretes ding, nicht wie du das in erinnerung hast)



  • Hi, danke!

    1. Das ist eine Sache, die ich noch gar nicht bedacht habe. Sch.... Was empfiehlst du? Eigene Implementierungen, oder stumpfe Benutzung von char* ?

    2. Wie meinst du das? Zwischen dem System und den Extensions werden quasi nur Pointer und Strings ausgetauscht. Probleme mit Speicherlecks sollte es nicht geben.

    3. Von beiden Versprechen habe ich noch nichts gehört, ich denke nicht dass ich diese beachten muss. Templates verwende ich eh nicht.

    Zum Edit: VALVe SDK für die Source Engine (hier mal ein Beispiel: http://pastebin.com/kpH4p83u)

    Gruß



  • theliquidwave schrieb:

    Hi, danke!

    1. Das ist eine Sache, die ich noch gar nicht bedacht habe. Sch.... Was empfiehlst du? Eigene Implementierungen, oder stumpfe Benutzung von char* ?

    Wenns geht Compiler vorschreiben. Bei MSVC geht das recht einfach (gibt ja nicht so viele Versionen), und wenn du nur Windows supporten musst, dann würde ich dir das wirklich empfehlen. Spart ne Menge Aufwand/Ärger/... BTW: Service Pack beachten!

    Wenn das nicht geht ... Mist.

    Im Prinzip ist nichtmal garantiert dass die ABI kompatibel ist, also dass z.B. sowas wie virtuelle Funktionen überhaupt zwischen verschiedenen Compilern funktioniert. Unter Windows normalerweise kein Problem, da ein Compiler unter Windows fast COM supporten muss, und wenn COM mit C++ (ohne Compiler-Magick) geht, dann gehen schonmal zumindest virtuelle Funktionen.
    Auf anderen Systemen ist u.U. gar nichts garantiert. Die Chancen stehen gut dass z.B. GCC 4.x zu GCC 4.y kompatibel ist, aber das solltest du selbst nachprüfen, ich weiss es einfach nicht.

    Um hier sinnvoll mehr sagen zu können müsste ich etwas mehr über dein Projekt wissen. z.B. Open Source vs. Closed Source, welche Betriebssysteme sollen/müssen unterstützt werden, wie umfangreich/komplex wird das Plugin Interface werden/was für Funktionen kann man da erwarten etc.

    1. Wie meinst du das? Zwischen dem System und den Extensions werden quasi nur Pointer und Strings ausgetauscht. Probleme mit Speicherlecks sollte es nicht geben.

    Es kann zu Problemen kommen wenn DLL A Speicher anfordert, und DLL B diesen wieder freigibt. Oder die EXE was anfordert und die DLL den Speicher wieder freigibt. Oder umgekehrt.
    Und sobald du STL Klassen verwendest, kannst du kaum noch garantieren, dass das nicht passiert.

    Beispielsweise kann std::string intern Reference-Counting verwenden. Ist in letzter Zeit wieder aus der Mode gekommen, aber es gab und gibt vermutlich noch Implementierungen die das machen. In so einem Fall kann man schwer bis gar nicht garantieren, dass Speicher auch immer dort freigegeben wird wo er angefordert wurde.

    Meine Empfehlung: das Problem dadurch umschiffen dass man nen genauen Compiler vorschreibt, bei dem bekannt ist, dass es kein Problem gibt.

    1. Von beiden Versprechen habe ich noch nichts gehört, ich denke nicht dass ich diese beachten muss. Templates verwende ich eh nicht.

    OK. Was ich noch vergessen hatte: Exceptions über DLL Grenzen hinweg können auch problematisch sein. Wenn die DLLs von verschiedenen Compilern erstellt wurden stehen die Chancen da ganz schlecht. Wenn es (genau) der selbe Compiler ist wieder relativ gut.

    Überhaupt kann ziemlich viel ziemlich schwierig werden wenn es NICHT der selbe Compiler in allen DLLs ist 🙂

    Zum Edit: VALVe SDK für die Source Engine (hier mal ein Beispiel: http://pastebin.com/kpH4p83u)

    Da kommt bei mir im Moment nur ein Error "502 Bad Gateway"



  • Zum Thema DLL's im wirklichen Leben gibts ein gutes Kapitel im Buch Imperfect C++ von Matthew Willson:
    Imperfect C++ | ISBN: 9780321228772

    Ich fands sehr gut.

    Simon



  • Hi.
    Danke @ hustbaer für den Roman 🙂

    Ich denke, dann wird es wohl auf eine Compilervorgabe hinauslaufen. Auf Linux ist man eh an den GCC 4.3.1 gebunden, da VALVe diesen vorschreibt. Auf Windows könnte man dann noch zwischen MSVC 2008 und 2010 variieren, da muss ich mal schauen wie ich das regeln kann.

    Im Projekt geht es darum, dass man für die Source Engine von VALVe Serverplugins nur mit C++ erstellen kann. Durch mein Plugin wird Python implementiert, so kann man also auch scripten. Nun soll es Autoren aber auch möglich sein, neue Funktionalitäten hinzuzufügen, eben durch die Extensions.

    Das mit der Versionierung von Interfaces verstehe ich immer noch nicht. Um das SDK zu erhalten brauchst du leider ein VALVe Spiel; ein anderes SDK kenne ich bisher nicht, wo das auftaucht. Ich habe mich nämlich ein bisschen eingelesen, und dort ist mir aufgefallen, dass virtuelle Funktionen immer der Reihenfolge nach in der VTable angelegt werden. Wenn man nun UNTEN neue Funktionen hinzufügt, sollte es also keine Probleme geben, da auch alte Interfaceversionen noch immer auf die richtigen Stellen zugreifen. Sie haben eben nur keine Chance auf neue Funktionen zuzugreifen.

    @ theta: Mal schauen. Ich denke aber nicht, dass ich mir extra dafür ein Buch kaufen werden. Trotzdem danke 👍

    Gruß



  • theliquidwave schrieb:

    1. Das ist eine Sache, die ich noch gar nicht bedacht habe. Sch.... Was empfiehlst du? Eigene Implementierungen, oder stumpfe Benutzung von char* ?

    Wenn dann const char*. Eine Referenz auf std::string als return einer pure-virtual-Funktion würd ich auch nicht machen, da dadurch jedes Plugin für jede Funktion einen std::string speichern muss, wenn auch nur als static in der jeweiligen Funktion.
    In deinem Fall verlierst du eigentlich nichts, wenn die Funktionen const char* zurückgeben.


Anmelden zum Antworten