3 Stimmen

Wie können Daten mit dynamisch geladenen Funktionen (dlopen) manipuliert/zurückgegeben werden?

Ich habe Tage damit verbracht, alle Tutorials, die ich zu diesem Thema gefunden habe, zu lesen und wieder zu lesen, und ich habe Stunden (und sogar Tage) damit verbracht, verwandte Fragen hier bei SO zu durchstöbern, aber ich kann das Folgende immer noch nicht zum Laufen bringen. Bitte entschuldigen Sie, wenn dies ein Duplikat ist: Es kann sein, dass ich die doppelten Fragen schon oft gesehen und gelesen habe, aber die Relevanz der Antworten für mein Problem nicht verstehen konnte. Damit ist das Thema erledigt...

Ich versuche, eine Plugin-Architektur für meine Anwendung zu implementieren. Plugins werden kompiliert und als Bibliotheken installiert. Zur Laufzeit verwendet die Anwendung dann dlopen() / dlsym(), um die Funktionen des Plugins zu laden und zu verknüpfen.
Die Idee ist, dass Plugins (Bibliotheken) eine Reihe von Funktionen implementieren, um Daten an die Hauptanwendung zurückzugeben oder von der Anwendung übergebene Daten zu manipulieren.

Um diese Idee zu testen, habe ich versucht, eine Funktion (innerhalb des Plugins) zu implementieren, die den (für Menschen lesbaren) Namen des Plugins selbst (als std::string) zurückgibt. Ich dachte, das wäre etwas Einfaches für den Anfang.... :-/

Hier ist, was ich bis jetzt habe:

// Plugin.cpp
extern "C" void plugin_name(std::string *name) {
        name = new std::string("Example plugin name");
}

// Application.cpp
void* handle = dlopen("libplugin.so", RTLD_LAZY);
typedef void (*plugin_t)(std::string*);
dlerror(); // Reset errors.
plugin_t call_plugin_name = (plugin_t) dlsym(handle, "plugin_name");
// ... Some error handling code.
std::string my_plugin_name;
call_plugin_name(&my_plugin_name);
dlclose(handle);
// More code that displays my_plugin_name.

Ich habe viele verschiedene Kombinationen ausprobiert, darunter eine, die einfacher zu sein schien (aber nicht besser funktionierte), bei der der Plugin-Name zurückgegeben wird:

// Plugin.cpp
extern "C" std::string plugin_name(void) {
        return std::string("Example plugin name");
}

Ich weiß, dass ich nah dran bin: der Code kompiliert und die Anwendung stürzt nicht mehr ab ;)
An der Stelle, an der ich den eigentlichen Plugin-Namen erwarten würde, ist jedoch ein leeres Feld zu sehen.

Alle Tutorials, die ich bisher gelesen habe, gehen sehr schnell über den Mechanismus, mit dem Daten in beide Richtungen weitergegeben werden: Plugin <=> Anwendung. Was ich mit einem "einfachen" std::string zu tun versuche, möchte ich später mit viel komplexeren Objekten tun (d.h. eine Plugin-Funktion würde ein Objekt als Referenz nehmen und einige seiner Eigenschaften ändern). Die Tutorien hören mehr oder weniger alle an dem Punkt auf, an dem ein Zeiger mit dlsym() erzeugt wird, und geben nicht viele Beispiele dafür, wie man diesen Zeiger verwendet.

Wie kann man das alles machen?

Eine weitere relevante Frage: verwende ich eine gemeinsame Kopfzeile, die ich sowohl mit der Anwendung als auch mit dem Plugin verwenden würde und in der ich die Signatur der Funktionsaufrufe definieren würde? Wie würde ich das machen und wie würde das helfen?

5voto

ssmir Punkte 1512

Die Signatur einer Funktion wird aus ihrem Namen und ihren Argumenttypen erzeugt (der Typ des Rückgabewerts spielt keine Rolle). Wenn Sie eine Funktion mit extern "C" deklarieren, wird das C-Symbolbenennungsschema verwendet, das offensichtlich nicht mit C++-Typen wie std::string umgehen kann. Aus diesem Grund funktioniert die Übergabe von std::string als Argument nicht.

Ich kann mir nicht erklären, warum die Rückgabe von std::string nicht funktioniert. Vielleicht werden unterschiedliche Aufrufkonventionen verwendet.

Der korrekte Weg, C++-Code aus einer gemeinsam genutzten Bibliothek zu importieren, besteht darin, Zeiger auf C++-Typen von Einstiegspunkten zurückzugeben. Und diese Einstiegspunkte müssen Argumente mit in C verfügbaren Typen haben. (Einstiegspunkt ist eine dokumentierte Funktion, die aus einer gemeinsam genutzten Bibliothek exportiert wird)

Hier ist ein guter Artikel über grundlegende Aspekte des Ladens von C++-Klassen aus gemeinsamen Bibliotheken. Dieser Artikel wird Ihre Frage umfassend beantworten.

Bitte beachten Sie, dass die Verwendung von Ausnahmen, die von einer gemeinsam genutzten Bibliothek in die Hauptanwendung geworfen werden, ihre Tücken hat. Und mit dynamic_cast von Objekten, die innerhalb einer Bibliothek erstellt wurden. Ich habe diese Themen erwähnt, damit Sie etwas vorbereitet sind, wenn Sie mit diesen Problemen konfrontiert werden.

[Bearbeiten]

Um meine Antwort zu verdeutlichen, möchte ich einige Beispiele anführen.

Um den Plugin-Namen zu erhalten, können Sie verwenden:

extern "C" const char * plugin_name() {
    return "Example plugin name";
}

// main.cc:
void* handle = dlopen("libplugin.so", RTLD_LAZY);
// ...
typedef const char * (*plugin_t)();
plugin_t call_plugin_name = (plugin_t) dlsym(handle, "plugin_name");
// ...
std::string my_plugin_name(call_plugin_name());
// use it

Um die Plugin-Funktionalität wirklich zu nutzen, sollten Sie eine Basisklasse in einer Kopfzeile deklarieren:

// plugin.h
class Plugin {
    public:
        virtual void doStuff() = 0;
        virtual ~Plugin() = 0;
};

// plugin.cc
Plugin::~Plugin() {
}

// myplugin.cc
class MyPlugin : public Plugin {
    virtual void doStuff() {
        std::cout << "Hello from plugin" << std::endl;
    }
};

extern "C" Plugin *createMyPluginInstance() {
    return new MyPlugin;
}

2voto

Mic Punkte 6405

Versuchen Sie es:

 extern "C" void plugin_name(std::string **name) {
     *name = new std::string("Example plugin name");
 }

 ...

 std::string *my_plugin_name;
 call_plugin_name(&my_plugin_name);

Da Sie eine Kopie des Zeigers zuweisen, den Sie als Argument übergeben haben, und nicht den, den Sie zuweisen wollten.

EDIT Hier ist es: Datei main.cpp

#include <iostream>
#include <dlfcn.h>
#include <string>

// Application.cpp
int main() {
    void* handle = dlopen("libplugin.so", RTLD_LAZY);
    typedef void (*plugin_t)(std::string**);
    dlerror(); // Reset errors.
    plugin_t call_plugin_name = (plugin_t) dlsym(handle, "plugin_name");
    // ... Some error handling code.
    std::string *my_plugin_name;
    call_plugin_name(&my_plugin_name);
    dlclose(handle);
    // More code that displays my_plugin_name.
    std::cout << "Plugin name is " << *my_plugin_name << std::endl;
    delete my_plugin_name;
    return 0;
}

Datei plugin.cpp

#include <string>

extern "C" void plugin_name(std::string **name) {
    *name = new std::string("example plugin name");
}

Nur ein Wort der Warnung . Obwohl dies kompiliert und ausgeführt wird, ist die Übergabe von C++-Typen über die DLL-Grenze riskant, und der obige Code ist nur Ihr Code, der fest genug ist, um zu kompilieren und auszuführen, er ist nicht sicher und hat eine sehr explizite Speicherbehandlung. Vielleicht möchten Sie das Problem auf eine andere Weise angehen.

1voto

bmargulies Punkte 94152

Bitte lesen Sie auch diese Frage und ihre Antworten. Es gibt viele Möglichkeiten für Inkompatibilitäten über die Grenzen der gemeinsam genutzten Bibliotheken in C++.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X