C ++ Dynamic Shared Library unter Linux

167

Dies ist eine Fortsetzung der Kompilierung von Dynamic Shared Library mit g ++ .

Ich versuche, eine gemeinsam genutzte Klassenbibliothek in C ++ unter Linux zu erstellen. Ich kann die Bibliothek zum Kompilieren bringen und einige der (nicht klassenbezogenen) Funktionen mithilfe der Tutorials aufrufen, die ich hier und hier gefunden habe . Meine Probleme beginnen, wenn ich versuche, die in der Bibliothek definierten Klassen zu verwenden. Das zweite Tutorial, mit dem ich verlinkt habe, zeigt, wie die Symbole zum Erstellen von Objekten der in der Bibliothek definierten Klassen geladen werden, verwendet diese Objekte jedoch nicht mehr, um Arbeiten auszuführen.

Kennt jemand eine komplette Tutorial für Shared C ++ Klassenbibliotheken erstellen , die auch zeigt , wie man verwenden diese Klassen in einem separaten ausführbaren? Ein sehr einfaches Tutorial, das die Erstellung, Verwendung von Objekten (einfache Getter und Setter wären in Ordnung) und das Löschen zeigt, wäre fantastisch. Ein Link oder ein Verweis auf einen Open Source-Code, der die Verwendung einer gemeinsam genutzten Klassenbibliothek veranschaulicht, wäre ebenso gut.


Obwohl die Antworten von codelogic und nimrodm funktionieren, wollte ich nur hinzufügen, dass ich seit dieser Frage eine Kopie von Beginning Linux Programming aufgenommen habe. Das erste Kapitel enthält Beispiel-C-Code und gute Erklärungen zum Erstellen und Verwenden von statischen und gemeinsam genutzten Bibliotheken . Diese Beispiele sind über die Google Buchsuche in einer älteren Ausgabe dieses Buches verfügbar .

Bill die Eidechse
quelle
Ich bin mir nicht sicher, ob ich verstehe, was Sie unter "Verwenden" verstehen. Sobald ein Zeiger auf das Objekt zurückgegeben wird, können Sie ihn wie jeden anderen Zeiger auf ein Objekt verwenden.
Codelogic
Der Artikel, auf den ich verlinkt habe, zeigt, wie mit dlsym ein Funktionszeiger auf eine Objektfactory-Funktion erstellt wird. Die Syntax zum Erstellen und Verwenden von Objekten aus der Bibliothek wird nicht angezeigt.
Bill the Lizard
1
Sie benötigen die Header-Datei, die die Klasse beschreibt. Warum müssen Sie Ihrer Meinung nach "dlsym" verwenden, anstatt das Betriebssystem die Bibliothek beim Laden suchen und verknüpfen zu lassen? Lassen Sie mich wissen, wenn Sie ein einfaches Beispiel benötigen.
Nimrodm
3
@nimrodm: Was ist die Alternative zur Verwendung von "dlsym"? Ich schreibe (sollte es sein) 3 C ++ - Programme, die alle die in der gemeinsam genutzten Bibliothek definierten Klassen verwenden. Ich habe auch 1 Perl-Skript, das es verwenden wird, aber das ist ein ganz anderes Problem für nächste Woche.
Bill the Lizard

Antworten:

154

myclass.h

#ifndef __MYCLASS_H__
#define __MYCLASS_H__

class MyClass
{
public:
  MyClass();

  /* use virtual otherwise linker will try to perform static linkage */
  virtual void DoSomething();

private:
  int x;
};

#endif

myclass.cc

#include "myclass.h"
#include <iostream>

using namespace std;

extern "C" MyClass* create_object()
{
  return new MyClass;
}

extern "C" void destroy_object( MyClass* object )
{
  delete object;
}

MyClass::MyClass()
{
  x = 20;
}

void MyClass::DoSomething()
{
  cout<<x<<endl;
}

class_user.cc

#include <dlfcn.h>
#include <iostream>
#include "myclass.h"

using namespace std;

int main(int argc, char **argv)
{
  /* on Linux, use "./myclass.so" */
  void* handle = dlopen("myclass.so", RTLD_LAZY);

  MyClass* (*create)();
  void (*destroy)(MyClass*);

  create = (MyClass* (*)())dlsym(handle, "create_object");
  destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");

  MyClass* myClass = (MyClass*)create();
  myClass->DoSomething();
  destroy( myClass );
}

Kompilieren Sie unter Mac OS X mit:

g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user

Kompilieren Sie unter Linux mit:

g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user

Wenn dies für ein Plugin-System wäre, würden Sie MyClass als Basisklasse verwenden und alle erforderlichen Funktionen virtuell definieren. Der Plugin Autor würde dann von MyClass ableiten, überschreibt die virtuals und implementieren create_objectund destroy_object. Ihre Hauptanwendung müsste in keiner Weise geändert werden.

codelogic
quelle
6
Ich bin gerade dabei, dies zu versuchen, habe aber nur eine Frage. Ist es unbedingt erforderlich, void * zu verwenden, oder kann die Funktion create_object stattdessen MyClass * zurückgeben? Ich bitte Sie nicht, dies für mich zu ändern. Ich möchte nur wissen, ob es einen Grund gibt, einen über den anderen zu verwenden.
Bill the Lizard
1
Vielen Dank, ich habe es versucht und es hat unter Linux über die Befehlszeile funktioniert (nachdem ich die Änderung vorgenommen habe, die Sie in den Codekommentaren vorgeschlagen haben). Ich schätze deine Zeit.
Bill the Lizard
1
Gibt es einen Grund, warum Sie diese mit externem "C" deklarieren würden? Da dies mit einem g ++ - Compiler kompiliert wird. Warum sollten Sie die Namenskonvention c verwenden? C kann c ++ nicht aufrufen. Eine in c ++ geschriebene Wrapper-Schnittstelle ist die einzige Möglichkeit, dies von c aus aufzurufen.
Ant2009
6
@ ant2009 brauchst du das, extern "C"weil die dlsymFunktion eine C-Funktion ist. Um die create_objectFunktion dynamisch zu laden , wird eine Verknüpfung im C-Stil verwendet. Wenn Sie das nicht verwenden würden extern "C", gäbe es keine Möglichkeit, den Namen der create_objectFunktion in der .so-Datei zu kennen, da der Name im C ++ - Compiler geändert wurde.
Kokx
1
Eine gute Methode, die der eines Microsoft-Compilers sehr ähnlich ist. Mit ein bisschen #if #else Arbeit können Sie ein schönes plattformunabhängiges System bekommen
Ha11owed
52

Das Folgende zeigt ein Beispiel für eine gemeinsam genutzte Klassenbibliothek, die gemeinsam genutzt wird. [H, cpp] und ein main.cpp-Modul, das die Bibliothek verwendet. Es ist ein sehr einfaches Beispiel und das Makefile könnte viel besser gemacht werden. Aber es funktioniert und kann Ihnen helfen:

shared.h definiert die Klasse:

class myclass {
   int myx;

  public:

    myclass() { myx=0; }
    void setx(int newx);
    int  getx();
};

shared.cpp definiert die getx / setx-Funktionen:

#include "shared.h"

void myclass::setx(int newx) { myx = newx; }
int  myclass::getx() { return myx; }

main.cpp verwendet die Klasse,

#include <iostream>
#include "shared.h"

using namespace std;

int main(int argc, char *argv[])
{
  myclass m;

  cout << m.getx() << endl;
  m.setx(10);
  cout << m.getx() << endl;
}

und das Makefile, das libshared.so generiert und main mit der gemeinsam genutzten Bibliothek verknüpft:

main: libshared.so main.o
    $(CXX) -o main  main.o -L. -lshared

libshared.so: shared.cpp
    $(CXX) -fPIC -c shared.cpp -o shared.o
    $(CXX) -shared  -Wl,-soname,libshared.so -o libshared.so shared.o

clean:
    $rm *.o *.so

Um 'main' auszuführen und mit libshared.so zu verknüpfen, müssen Sie wahrscheinlich den Ladepfad angeben (oder ihn in / usr / local / lib oder ähnlichem ablegen).

Im Folgenden wird das aktuelle Verzeichnis als Suchpfad für Bibliotheken angegeben und main (Bash-Syntax) ausgeführt:

export LD_LIBRARY_PATH=.
./main

Um zu sehen, dass das Programm mit libshared.so verknüpft ist, können Sie ldd ausprobieren:

LD_LIBRARY_PATH=. ldd main

Druckt auf meinem Computer:

  ~/prj/test/shared$ LD_LIBRARY_PATH=. ldd main
    linux-gate.so.1 =>  (0xb7f88000)
    libshared.so => ./libshared.so (0xb7f85000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e74000)
    libm.so.6 => /lib/libm.so.6 (0xb7e4e000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xb7e41000)
    libc.so.6 => /lib/libc.so.6 (0xb7cfa000)
    /lib/ld-linux.so.2 (0xb7f89000)
Nimrodm
quelle
1
Dies scheint (für mein sehr ungeübtes Auge) eine statische Verknüpfung von libshared.so mit Ihrer ausführbaren Datei zu sein, anstatt zur Laufzeit eine dynamische Verknüpfung zu verwenden. Hab ich recht?
Bill the Lizard
10
Nein. Dies ist eine dynamische Standard-Unix-Verknüpfung (Linux). Eine dynamische Bibliothek hat die Erweiterung ".so" (Shared Object) und wird beim Laden mit der ausführbaren Datei (in diesem Fall main) verknüpft - jedes Mal, wenn main geladen wird. Die statische Verknüpfung erfolgt zur Verknüpfungszeit und verwendet Bibliotheken mit der Erweiterung ".a" (Archiv).
Nimrodm
9
Dies wird zur Erstellungszeit dynamisch verknüpft . Mit anderen Worten, Sie benötigen Vorkenntnisse über die Bibliothek, mit der Sie verknüpfen (z. B. Verknüpfung mit 'dl' für dlopen). Dies unterscheidet sich vom dynamischen Laden einer Bibliothek, beispielsweise basierend auf einem benutzerdefinierten Dateinamen, für den keine Vorkenntnisse erforderlich sind.
Codelogic
10
Was ich (schlecht) erklären wollte, ist, dass Sie in diesem Fall den Namen der Bibliothek zum Zeitpunkt der Erstellung kennen müssen (Sie müssen -lshared an gcc übergeben). Normalerweise verwendet man dlopen (), wenn diese Informationen nicht verfügbar sind, dh der Name der Bibliothek wird zur Laufzeit ermittelt (z. B. Plugin-Aufzählung).
Codelogic
3
Verwenden Sie diese Option -L. -lshared -Wl,-rpath=$$(ORIGIN)beim Verknüpfen und Löschen LD_LIBRARY_PATH=..
Maxim Egorushkin
9

Grundsätzlich sollten Sie die Header-Datei der Klasse in den Code aufnehmen, in dem Sie die Klasse in der gemeinsam genutzten Bibliothek verwenden möchten. Verwenden Sie dann beim Verknüpfen das Flag '-l' , um Ihren Code mit der gemeinsam genutzten Bibliothek zu verknüpfen. Dazu muss die .so natürlich dort sein, wo das Betriebssystem sie finden kann. Siehe 3.5. Installieren und Verwenden einer gemeinsam genutzten Bibliothek

Die Verwendung von dlsym ist gedacht, wenn Sie zur Kompilierungszeit nicht wissen, welche Bibliothek Sie verwenden möchten. Das hört sich hier nicht so an. Vielleicht besteht die Verwirrung darin, dass Windows die dynamisch geladenen Bibliotheken aufruft, unabhängig davon, ob Sie die Verknüpfung zur Kompilierung oder zur Laufzeit (mit analogen Methoden) durchführen. Wenn ja, können Sie sich dlsym als das Äquivalent von LoadLibrary vorstellen.

Wenn Sie die Bibliotheken wirklich dynamisch laden müssen (dh sie sind Plug-Ins), sollten diese FAQ helfen.

Matt Lewis
quelle
1
Der Grund, warum ich eine dynamische gemeinsam genutzte Bibliothek benötige, ist, dass ich sie auch über Perl-Code aufrufe. Es kann ein völliges Missverständnis sein, dass ich es auch dynamisch aus anderen C ++ - Programmen aufrufen muss, die ich entwickle.
Bill the Lizard
Ich habe noch nie versucht, Perl und C ++ zu integrieren, aber ich denke, Sie müssen XS verwenden: johnkeiser.com/perl-xs-c++.html
Matt Lewis
5

Zusätzlich zu den vorherigen Antworten möchte ich das Bewusstsein dafür schärfen, dass Sie die RAII-Sprache (Resource Acquisition Is Initialization) verwenden sollten, um die Zerstörung von Handlern sicher zu machen.

Hier ist ein vollständiges Arbeitsbeispiel:

Interface - Deklaration: Interface.hpp:

class Base {
public:
    virtual ~Base() {}
    virtual void foo() const = 0;
};

using Base_creator_t = Base *(*)();

Gemeinsamer Bibliotheksinhalt:

#include "Interface.hpp"

class Derived: public Base {
public:
    void foo() const override {}
};

extern "C" {
Base * create() {
    return new Derived;
}
}

Dynamische Shared Library - Handler: Derived_factory.hpp:

#include "Interface.hpp"
#include <dlfcn.h>

class Derived_factory {
public:
    Derived_factory() {
        handler = dlopen("libderived.so", RTLD_NOW);
        if (! handler) {
            throw std::runtime_error(dlerror());
        }
        Reset_dlerror();
        creator = reinterpret_cast<Base_creator_t>(dlsym(handler, "create"));
        Check_dlerror();
    }

    std::unique_ptr<Base> create() const {
        return std::unique_ptr<Base>(creator());
    }

    ~Derived_factory() {
        if (handler) {
            dlclose(handler);
        }
    }

private:
    void * handler = nullptr;
    Base_creator_t creator = nullptr;

    static void Reset_dlerror() {
        dlerror();
    }

    static void Check_dlerror() {
        const char * dlsym_error = dlerror();
        if (dlsym_error) {
            throw std::runtime_error(dlsym_error);
        }
    }
};

Kundencode:

#include "Derived_factory.hpp"

{
    Derived_factory factory;
    std::unique_ptr<Base> base = factory.create();
    base->foo();
}

Hinweis:

  • Ich habe alles aus Gründen der Übersichtlichkeit in Header-Dateien abgelegt. Im wirklichen Leben sollten Sie Ihren Code natürlich zwischen .hppund .cppDateien aufteilen .
  • Zur Vereinfachung habe ich den Fall ignoriert, in dem Sie eine new/ deleteÜberladung behandeln möchten .

Zwei übersichtliche Artikel, um weitere Details zu erhalten:

Xavier Lamorlette
quelle
Dies ist ein hervorragendes Beispiel. RAII ist definitiv der richtige Weg.
David Steinhauer