Dynamiczna biblioteka współdzielona C ++ w systemie Linux


167

To jest kontynuacja kompilacji Dynamic Shared Library z g ++ .

Próbuję utworzyć współdzieloną bibliotekę klas w C ++ w systemie Linux. Jestem w stanie skompilować bibliotekę i mogę wywołać niektóre (nieklasowe) funkcje, korzystając z samouczków, które znalazłem tutaj i tutaj . Moje problemy zaczynają się, gdy próbuję użyć klas zdefiniowanych w bibliotece. Drugi samouczek, do którego dołączyłem, pokazuje, jak załadować symbole do tworzenia obiektów klas zdefiniowanych w bibliotece, ale zatrzymuje się na krótko przed użyciem tych obiektów do wykonania jakiejkolwiek pracy.

Czy ktoś zna bardziej kompletny samouczek dotyczący tworzenia współdzielonych bibliotek klas C ++, który pokazuje również, jak używać tych klas w oddzielnym pliku wykonywalnym? Bardzo prosty samouczek pokazujący tworzenie i używanie obiektów (proste metody pobierające i ustawiające byłyby w porządku) i usuwanie byłoby fantastyczne. Równie dobry byłby odsyłacz lub odniesienie do jakiegoś kodu open source, który ilustruje użycie współdzielonej biblioteki klas.


Chociaż odpowiedzi z codelogic i nimrodm działają, chciałem tylko dodać, że wybrałem kopię Beginning Linux Programming od czasu zadania tego pytania, a jej pierwszy rozdział zawiera przykładowy kod C i dobre wyjaśnienia dotyczące tworzenia i używania zarówno bibliotek statycznych, jak i współdzielonych . Te przykłady są dostępne za pośrednictwem Google Book Search w starszym wydaniu tej książki .


Nie jestem pewien, czy rozumiem, co masz na myśli, mówiąc o „używaniu” go. Gdy wskaźnik do obiektu zostanie zwrócony, możesz go używać tak, jak każdego innego wskaźnika do obiektu.
— codelogic

Artykuł, do którego utworzyłem łącze, pokazuje, jak utworzyć wskaźnik funkcji do funkcji fabryki obiektów przy użyciu dlsym. Nie pokazuje składni tworzenia i używania obiektów z biblioteki.
— Bill the Lizard

1
Będziesz potrzebował pliku nagłówkowego opisującego klasę. Jak myślisz, dlaczego musisz używać „dlsym” zamiast po prostu pozwalać systemowi operacyjnemu na znajdowanie i łączenie biblioteki w czasie ładowania? Daj mi znać, jeśli potrzebujesz prostego przykładu.
— nimrodm

3
@nimrodm: Jaka jest alternatywa dla używania „dlsym”? Mam (powinienem) pisać 3 programy C ++, które będą używać klas zdefiniowanych w bibliotece współdzielonej. Mam też 1 skrypt w Perlu, który go użyje, ale to zupełnie inny problem na następny tydzień.
— Bill the Lizard

Odpowiedzi:


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 );
}

W systemie Mac OS X skompiluj z:

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

W systemie Linux skompiluj z:

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

Gdyby to było dla systemu wtyczek, użyłbyś MyClass jako klasy bazowej i zdefiniowałbyś wszystkie wymagane funkcje wirtualne. Autor wtyczki następnie wywodziłby się z MyClass, nadpisałby wirtualne i zaimplementował create_objecti destroy_object. Twoja główna aplikacja nie musiałaby być w żaden sposób zmieniana.


6
Właśnie tego próbuję, ale mam tylko jedno pytanie. Czy użycie void * jest absolutnie konieczne, czy też funkcja create_object może zwrócić MyClass *? Nie proszę, żebyś to dla mnie zmieniał, po prostu chciałbym wiedzieć, czy istnieje powód, aby używać jednego nad drugim.
— Bill the Lizard

1
Dzięki, wypróbowałem to i działało tak, jak w Linuksie z wiersza poleceń (kiedy wprowadziłem zmianę, którą zasugerowałeś w komentarzach do kodu). Szanuję Twój czas.
— Bill the Lizard

1
Czy jest jakiś powód, dla którego miałbyś zadeklarować je z zewnętrznym „C”? Ponieważ jest to kompilowane przy użyciu kompilatora g ++. Dlaczego miałbyś chcieć używać konwencji nazewnictwa c? C nie może wywołać C ++. Interfejs opakowania napisany w c ++ jest jedynym sposobem wywołania tego z poziomu c.
— ant2009

6
@ ant2009 potrzebujesz, extern "C"ponieważ dlsymfunkcja jest funkcją C. Aby dynamicznie załadować create_objectfunkcję, użyje powiązania w stylu C. Gdybyś nie użył rozszerzenia extern "C", nie byłoby możliwości poznania nazwy create_objectfunkcji w pliku .so z powodu zmiany nazw w kompilatorze C ++.
— kokx,

1
Niezła metoda, jest bardzo podobna do tego, co ktoś zrobiłby na kompilatorze Microsoftu. przy odrobinie #if #else pracy, możesz uzyskać niezły system niezależny od platformy
— Ha11owed

52

Poniżej przedstawiono przykład współużytkowanej biblioteki klas. [H, cpp] i modułu main.cpp korzystającego z tej biblioteki. To bardzo prosty przykład, a plik makefile mógłby być znacznie lepszy. Ale to działa i może ci pomóc:

shared.h definiuje klasę:

class myclass {
   int myx;

  public:

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

shared.cpp definiuje funkcje getx / setx:

#include "shared.h"

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

main.cpp używa klasy,

#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;
}

oraz plik makefile, który generuje libshared.so i łączy main z biblioteką współdzieloną:

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

Aby faktycznie uruchomić 'main' i połączyć się z libshared., prawdopodobnie będziesz musiał określić ścieżkę ładowania (lub umieścić ją w / usr / local / lib lub podobnym).

Poniższy kod określa bieżący katalog jako ścieżkę wyszukiwania bibliotek i uruchamia main (składnia bash):

export LD_LIBRARY_PATH=.
./main

Aby zobaczyć, że program jest powiązany z libshared. więc możesz spróbować ldd:

LD_LIBRARY_PATH=. ldd main

Wydruki na moim komputerze:

  ~/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)

1
Wydaje się to (moim bardzo niewprawnym okiem) statyczne łączenie libshared.so z twoim plikiem wykonywalnym, a nie używanie dynamicznego linkowania w czasie wykonywania. Mam rację?
— Bill the Lizard

10
Nie. To jest standardowe łączenie dynamiczne w systemie Unix (Linux). Biblioteka dynamiczna ma rozszerzenie „.so” (obiekt współdzielony) i jest połączona z plikiem wykonywalnym (w tym przypadku głównym) w czasie ładowania - za każdym razem, gdy ładowany jest plik main. Łączenie statyczne następuje w czasie łączenia i korzysta z bibliotek z rozszerzeniem „.a” (archiwum).
— nimrodm

9
Jest to dynamicznie łączone w czasie kompilacji . Innymi słowy, potrzebujesz wcześniejszej wiedzy na temat biblioteki, z którą tworzysz linki (np. Tworzenie linków z „dl” dla dlopen). Różni się to od dynamicznego ładowania biblioteki, na przykład na podstawie nazwy pliku określonej przez użytkownika, gdzie wcześniejsza wiedza nie jest potrzebna.
— codelogic

10
To, co próbowałem wyjaśnić (źle), to to, że w tym przypadku musisz znać nazwę biblioteki w czasie kompilacji (musisz przekazać -lshared do gcc). Zwykle używa się dlopen (), gdy ta informacja nie jest dostępna, tj. Nazwa biblioteki jest odkrywana w czasie wykonywania (np. Wyliczenie wtyczki).
— codelogic

3
Użyj -L. -lshared -Wl,-rpath=$$(ORIGIN)podczas łączenia i upuść to LD_LIBRARY_PATH=..
— Maxim Egorushkin

9

Zasadniczo powinieneś dołączyć plik nagłówkowy klasy do kodu, w którym chcesz użyć klasy w bibliotece współdzielonej. Następnie podczas tworzenia linku użyj flagi „-l”, aby połączyć swój kod z udostępnioną biblioteką. Oczywiście wymaga to, aby plik .so był tam, gdzie system operacyjny może go znaleźć. Patrz 3.5. Instalowanie i używanie udostępnionej biblioteki

Korzystanie z dlsym jest przydatne, gdy w czasie kompilacji nie wiesz, której biblioteki chcesz użyć. To nie brzmi, jakby tak było w tym przypadku. Może zamieszanie polega na tym, że system Windows wywołuje dynamicznie ładowane biblioteki, niezależnie od tego, czy łączysz się w czasie kompilacji, czy w czasie wykonywania (za pomocą analogicznych metod)? Jeśli tak, możesz myśleć o dlsym jako odpowiedniku LoadLibrary.

Jeśli naprawdę potrzebujesz dynamicznie ładować biblioteki (tj. Są to wtyczki), to ten FAQ powinien pomóc.


1
Powodem, dla którego potrzebuję dynamicznej biblioteki współdzielonej, jest to, że będę ją również wywoływał z kodu Perla. Z mojej własnej strony może to być kompletne nieporozumienie, że muszę również wywoływać to dynamicznie w innych programach C ++, które rozwijam.
— Bill the Lizard

Nigdy nie próbowałem zintegrowanego Perla i C ++, ale myślę, że musisz użyć XS: johnkeiser.com/perl-xs-c++.html
— Matt Lewis,

5

Oprócz poprzednich odpowiedzi chciałbym zwrócić uwagę na fakt, że należy używać idiomu RAII (pozyskiwanie zasobów to inicjalizacja) aby być bezpiecznym w przypadku zniszczenia obsługi.

Oto kompletny przykład roboczy:

Deklaracja interfejsu Interface.hpp::

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

using Base_creator_t = Base *(*)();

Zawartość biblioteki współdzielonej:

#include "Interface.hpp"

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

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

Dynamiczna procedura obsługi bibliotek współdzielonych 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);
        }
    }
};

Kod klienta:

#include "Derived_factory.hpp"

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

Uwaga:

  • Dla zwięzłości umieszczam wszystko w plikach nagłówkowych. W prawdziwym życiu powinieneś oczywiście podzielić swój kod pomiędzy .hppi.cpp pliki .
  • Aby uprościć, zignorowałem przypadek, w którym chcesz obsłużyć new/ deleteprzeciążenie.

Dwa jasne artykuły, aby uzyskać więcej informacji:


To doskonały przykład. RAII jest zdecydowanie właściwą drogą.
— David Steinhauer
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.