Hilfe bei Plugin-System



  • Hi,
    ich bräuchte mal wieder etwas Hilfe oder Anregungen.
    Diesmal geht es um ein Plugin-System, das ich in mein Programm zur Bildverarbeitung einbauen möchte. Mit dem Plugin-System möchte ich relativ einfach weitere Filter und Algorithmen hinzufügen. Zurzeit soll es erstmal nur für Linux laufen, Cross-Plattform-Unterstützung bau ich evtl. später, wenn ich Bedarf sehe...

    Ich bin soweit, dass ich eine einfache Pluginsystem-API habe und auch schon erfolgreich Plugins laden und ausführen kann.

    Mein Problem jetzt ist, dass ich auch z.b. mehrere Filter, also Plugins, in einer Library haben möchte.

    Die System-API:

    #ifndef FIPA_PLUGIN_SYSTEM_API_H
    #define FIPA_PLUGIN_SYSTEM_API_H
    
    #include <memory>
    
    namespace FIPA {
    namespace Plugin {
    
    //Forward declaration of base class for all plugins
    class FipaPlugin;
    
    #define FIPA_PLUGIN_API_VERSION 1
    
    //Plugin details
    struct FipaPluginDetails
    {
        int apiVersion;
        const char* fileName;
        const char* className;
        const char* pluginName;
        const char* pluginVersion;
        const char* author;
        const char* description;
    };
    
    #define FIPA_PLUGIN(classType, pluginName, pluginVersion, author, description)       \
        extern "C" {                                                        \
        std::shared_ptr<FIPA::Plugin::FipaPlugin> loadPlugin()              \
        {                                                                   \
            return std::make_shared<classType>();                           \
        }                                                                   \
        FIPA::Plugin::FipaPluginDetails details =                           \
        {                                                                   \
            FIPA_PLUGIN_API_VERSION,                                        \
            __FILE__,                                                       \
            #classType,                                                     \
            pluginName,                                                     \
            pluginVersion,                                                  \
            author,                                                         \
            description,                                                    \
        };                                                                  \
     }
    
    }} //namespace Fipa::Plugin
    
    #endif // FIPA_PLUGIN_SYSTEM_API_H
    
    

    Wenn ich jetzt jede Filterklasse mit

    FIPA_PLUGIN(Filter_GaussianBlur, "GaussianBlur", "1.0", "Me", "gaussian blur standard filter")
    FIPA_PLUGIN(Filter_MedianBlur, "MedianBlur", "1.0", "Me", "median blur standard filter")
    
    

    registriere, meckert der Compiler natürlich, dass es mehrfache Definitionen von FIPA_PLUGIN gibt.

    Wie kann ich das also so bauen, dass ich möglichst einfach neue Pluginklassen erstellen kann, ohne für jedes Plugin eine eigene Lib zu haben?



  • Naja du könntest z.B. einfach aus details ein Array machen und den Index dann bei loadPlugin mitgeben. Irgendwoher muss man dann natürlich auch noch die Info bekommen wie viele Einträge in dem Array sind, aber das ist auch keine Hexerei. Was dann natürlich nicht mehr so gut mit deinen Makros funktioniert.

    Wenn du das ganze etwas umbaust, geht es vermutlich einfacher. Entferne loadPlugin und pack statt dessen einen Funktionszeiger mit in die FipaPluginDetails struct.

    BTW: Eine einfache Möglichkeit die "wie gross ist das Array" Sache zu lösen (bzw. zu vermeiden) wäre wenn du statt des Arrays eine Funktion exportierst die einen size_t als Parameter nimmt und einen FipaPluginDetails const* zurückgibt. Das "äussere" Programm kann die Funktion dann einfach so lange aufrufen bis sie nullptr zurückliefert.



  • Ein shared_ptr in extern "C" ... ernsthaft? Da hab ich aufgehört zu lesen.



  • @Swordfish
    shared_ptr mag im Zusammenhang mit nem Plugin System nicht die beste Wahl sein, aber shared_ptr und extern "C"... wo soll sich das bitte beissen? Magst das mal erklären?



  • @hustbaer sagte in Hilfe bei Plugin-System:

    shared_ptr mag im Zusammenhang mit nem Plugin System nicht die beste Wahl sein, aber shared_ptr und extern "C"... wo soll sich das bitte beissen? Magst das mal erklären?

    Gebissen wird hier sicherlich nichts und niemand. Aber extern "C" wurde erfunden, damit C-funktionen auch C++-funktionen aufrufen können. Wenn diese Funktionen, dann aber Typen zurückgeben, mit denen C-funktionen nichts anfangen können, macht das wenig Sinn.

    Hier bewirkt es nur, daß die ganzen namespaces keine Auswirkung auf das Symbol mehr haben und wenn eine Third-Party-Bibliothek auf die selbe Idee kommt, darfst Du lange nach dem Fehler suchen.

    VG Martin



  • @mgaeckler
    Hi 🙂
    Wofür extern "C" erfunden wurde ist ja wörscht, es geht darum was es macht. Und was es macht lässt sich wunderbar verwenden um DLLs/SOs zu machen deren Zeugs man dann schön mit GetProcAddress/dlsym reinholen kann. Weil halt "zahmer" Name.

    (BTW: Ich gehe eher davon aus dass es umgekehrt ist, also dass extern "C" primär dafür gemacht wurde dass man in einem C++ Programm auf C Funktionen zugreifen kann. Denn selbst wenn man im globalen Namespace Zeugs ohne extern "C" deklariert wird dabei nie der Name rauskommen der in der C Library verwendet wird.)

    Was Third-Party-Bibliotheken angeht... so lange der Name "unique" genug ist, gibt's da nicht wirklich ein Problem. Bzw. kein grösseres als mit Namespaces. Denn selbst da kann es clashen, nämlich wenn die Third-Party-Bibliothek den selben Namespace verwendet.

    loadPlugin wie im Beispiel vom OP ist vermutlich als Name net so toll. Aber davon war ja auch nicht die Rede.



  • @hustbaer
    Der umgekehrte Weg von C++ C-funktionen aufzurufen ist natürlich auch ein wichtiger Anwendungsfall. Dein Einwand mit DLLs bzw. Shared Libraries ist aber wirklich ein Beispiel, wo die gezeigte Kombination sinnvolll erscheint. Daran habe ich jetzt in der Tat überhaupt ned gedacht.



  • @mgaeckler Und nachdem es hier um ein Plugin-System geht...



  • Erstmal danke für die Antworten 🙂

    der shared_ptr im extern "C" war eher ein Ausrutscher, ich wollte nur probieren, ob das auch geht, erschien mir aber von vornherein "unsauber". Was Makros und Libs angeht bin ich noch blutiger Anfänger...
    Ich habe das jetzt durch eine singleton factory mit entsprechendem Macro gelöst.

    Aber zu den Macros und meinem "Design" (wenn man es denn so nennen will) habe ich noch 2,3 Fragen.
    (1) Zuerst mal, ist mein "Design" sauber? Sollte die factory in die Plugin-Lib mit eingebunden werden oder gehört sie ins "Hauptprogramm"? Nach meiner Ansicht muss sie mit in die Libs, da man ja sonst die Plugins nicht registrieren kann...

    (2) Zurzeit habe ich

    #define FIPA_FACTORY()            \
        extern "C" {                  \
        FIPA_EXPORT void* factory()   \
        {                             \
           return FIPA::Plugin::PluginFactory::instance();  \
        }                                                   \
        }
    

    in der SystemAPI, um die factory bzw. die instance()-Methode von der Lib zu bekommen.
    Daher muss ich in den konkreten Plugin FIPA_FACTORY() aufrufen. Wie kann ich das Symbol ohne den Aufruf bereitstellen? Wenn ich das "#define" weglasse meckert der Compiler wieder, das es multiple Definitionen gibt, da ich auch mal mehrere Header für die Plugin-Klassen habe...



  • Warum definierst Du Deine Factory nicht wie jeder andere auch in eine cpp-Datei und lässt Dein define weg`?



  • @hustbaer sagte in Hilfe bei Plugin-System:

    @Swordfish
    shared_ptr mag im Zusammenhang mit nem Plugin System nicht die beste Wahl sein, aber shared_ptr und extern "C"... wo soll sich das bitte beissen? Magst das mal erklären?

    Naja, extern "C" sagt per Definition daß die enthaltenen Deklarationen C sind. Aber wenns funktioniert ... what shells.

    //edit: ach ja, da muss man noch weiter schaun ... language-linkage ... sollte also garantiert funktionieren wenn man kein overloading betreibt.


Anmelden zum Antworten