Libreria condivisa dinamica C ++ su Linux


167

Questo è il seguito della compilazione di Dynamic Shared Library con g ++ .

Sto cercando di creare una libreria di classi condivisa in C ++ su Linux. Sono in grado di compilare la libreria e posso chiamare alcune delle funzioni (non di classe) usando i tutorial che ho trovato qui e qui . I miei problemi iniziano quando provo ad usare le classi definite nella libreria. Il secondo tutorial a cui ho collegato mostra come caricare i simboli per la creazione di oggetti delle classi definite nella libreria, ma smette di usare quegli oggetti per fare qualsiasi lavoro.

Qualcuno sa di un tutorial più completo per la creazione di librerie di classi C ++ condivise che mostra anche come utilizzare quelle classi in un eseguibile separato? Un tutorial molto semplice che mostra la creazione, l'utilizzo di oggetti (semplici getter e setter andrebbero bene) e la cancellazione sarebbe fantastica. Un collegamento o un riferimento ad un codice open source che illustra l'uso di una libreria di classi condivisa sarebbe altrettanto valido.


Sebbene le risposte di codelogic e nimrodm funzionino, volevo solo aggiungere che ho preso una copia di Beginning Linux Programming da quando ho posto questa domanda, e il suo primo capitolo ha un codice C di esempio e buone spiegazioni per la creazione e l'uso di librerie statiche e condivise . Questi esempi sono disponibili tramite Google Ricerca Libri in un'edizione precedente di quel libro .


Non sono sicuro di capire cosa intendi per "utilizzarlo", una volta restituito un puntatore all'oggetto, potresti usarlo come se usassi qualsiasi altro puntatore a un oggetto.
codelogic,

L'articolo a cui ho collegato mostra come creare un puntatore a una funzione di factory di oggetti usando dlsym. Non mostra la sintassi per la creazione e l'utilizzo di oggetti dalla libreria.
Bill the Lizard,

1
Sarà necessario il file di intestazione che descriva la classe. Perché pensi di dover usare "dlsym" invece di lasciare che il sistema operativo trovi e colleghi la libreria al momento del caricamento? Fammi sapere se hai bisogno di un semplice esempio.
nimrodm,

3
@nimrodm: qual è l'alternativa all'utilizzo di "dlsym"? Sto (presumibilmente) scrivendo 3 programmi C ++ che useranno tutti le classi definite nella libreria condivisa. Ho anche 1 script Perl che lo userà, ma questo è un altro problema per la prossima settimana.
Bill the Lizard,

Risposte:


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

Su Mac OS X, compila con:

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

Su Linux, compilare con:

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

Se questo fosse per un sistema di plugin, useresti MyClass come classe di base e definiresti tutte le funzioni richieste virtuali. L'autore del plugin verrebbe quindi da MyClass, sovrascriverà i virtuali e implementerà create_objecte destroy_object. La tua applicazione principale non dovrebbe essere modificata in alcun modo.


6
Sto provando questo, ma ho solo una domanda. È strettamente necessario utilizzare void *, oppure la funzione create_object potrebbe restituire MyClass *? Non ti sto chiedendo di cambiarlo per me, vorrei solo sapere se c'è un motivo per usarne uno rispetto all'altro.
Bill the Lizard,

1
Grazie, ho provato questo e ha funzionato come su Linux dalla riga di comando (una volta apportata la modifica suggerita nei commenti sul codice). Apprezzo il tuo tempo.
Bill the Lizard,

1
C'è qualche motivo per dichiararli con la "C"? Dato che questo è compilato usando un compilatore g ++. Perché dovresti usare la convenzione di denominazione in c? C non può chiamare c ++. Un'interfaccia wrapper scritta in c ++ è l'unico modo per chiamarlo da c.
ant2009,

6
@ ant2009 è necessario extern "C"perché la dlsymfunzione è una funzione C. E per caricare dinamicamente la create_objectfunzione, utilizzerà il collegamento in stile C. Se non si utilizzasse il extern "C", non ci sarebbe modo di conoscere il nome della create_objectfunzione nel file .so, a causa della modifica del nome nel compilatore C ++.
Kokx,

1
Bel metodo, è molto simile a quello che qualcuno farebbe su un compilatore Microsoft. con un po 'di lavoro #if #else, puoi ottenere un bel sistema indipendente dalla piattaforma
Ha11owed

52

Di seguito è mostrato un esempio di una libreria di classi condivisa condivisa. [H, cpp] e un modulo main.cpp che utilizza la libreria. È un esempio molto semplice e il makefile potrebbe essere migliorato molto. Ma funziona e può aiutarti:

shared.h definisce la classe:

class myclass {
   int myx;

  public:

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

shared.cpp definisce le funzioni getx / setx:

#include "shared.h"

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

main.cpp usa la classe,

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

e il makefile che genera libshared.so e collega main con la libreria condivisa:

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

Per eseguire effettivamente 'main' e collegarsi a libshared.so probabilmente dovrai specificare il percorso di caricamento (o metterlo in / usr / local / lib o simili).

Quanto segue specifica la directory corrente come percorso di ricerca per le librerie ed esegue main (sintassi bash):

export LD_LIBRARY_PATH=.
./main

Per vedere che il programma è collegato a libshared.so puoi provare ldd:

LD_LIBRARY_PATH=. ldd main

Stampe sulla mia macchina:

  ~/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
Questo sembra (al mio occhio molto inesperto) il collegamento statico di libshared.so al tuo eseguibile, piuttosto che l'utilizzo del collegamento dinamico in fase di esecuzione. Ho ragione?
Bill the Lizard,

10
No. Questo è il collegamento dinamico standard Unix (Linux). Una libreria dinamica ha l'estensione ".so" (Shared Object) ed è collegata all'eseguibile (main in questo caso) al momento del caricamento - ogni volta che main viene caricata. Il collegamento statico si verifica al momento del collegamento e utilizza le librerie con l'estensione ".a" (archivio).
nimrodm,

9
Questo è collegato in modo dinamico al momento della creazione . In altre parole, è necessario avere una conoscenza preliminare della libreria a cui ci si collega (ad es. Collegamento a 'dl' per dlopen). Ciò è diverso dal caricamento dinamico di una libreria, in base, ad esempio, a un nome file specificato dall'utente, dove non è necessaria una conoscenza preliminare.
codelogic

10
Quello che stavo cercando di spiegare (male) è che in questo caso, devi conoscere il nome della libreria al momento della compilazione (devi passare -condiviso a gcc). Di solito, si usa dlopen () quando tali informazioni non sono disponibili, ovvero il nome della libreria viene scoperto in fase di esecuzione (ad esempio: enumerazione dei plugin).
codelogic

3
Uso -L. -lshared -Wl,-rpath=$$(ORIGIN) per il collegamento e rilasciarlo LD_LIBRARY_PATH=..
Maxim Egorushkin,

9

Fondamentalmente, è necessario includere il file di intestazione della classe nel codice in cui si desidera utilizzare la classe nella libreria condivisa. Quindi, quando si collega, utilizzare il flag '-l' per collegare il codice con la libreria condivisa. Ovviamente, questo richiede che .so sia dove il sistema operativo può trovarlo. Vedi 3.5. Installazione e utilizzo di una libreria condivisa

L'uso di dlsym è per quando non si conosce al momento della compilazione quale libreria si desidera utilizzare. Non sembra che sia il caso qui. Forse la confusione è che Windows chiama le librerie caricate dinamicamente se si esegue il collegamento in fase di compilazione o runtime (con metodi analoghi)? In tal caso, puoi pensare a dlsym come l'equivalente di LoadLibrary.

Se hai davvero bisogno di caricare dinamicamente le librerie (ad esempio, sono plug-in), allora queste FAQ dovrebbero aiutare.


1
Il motivo per cui ho bisogno di una libreria dinamica condivisa è che la chiamerò anche dal codice Perl. Potrebbe essere un malinteso completo da parte mia che devo anche chiamarlo dinamicamente da altri programmi C ++ che sto sviluppando.
Bill the Lizard,

Non ho mai provato il perl e il C ++ integrati, ma penso che devi usare XS: johnkeiser.com/perl-xs-c++.html
Matt Lewis

5

Oltre alle risposte precedenti, vorrei sensibilizzare sul fatto che dovresti usare il linguaggio RAII (Resource Acquisition Is Initialisation) per essere al sicuro sulla distruzione degli handler.

Ecco un esempio di lavoro completo:

Dichiarazione di interfaccia Interface.hpp::

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

using Base_creator_t = Base *(*)();

Contenuto della libreria condivisa:

#include "Interface.hpp"

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

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

Gestore dinamico della libreria condivisa 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);
        }
    }
};

Codice cliente:

#include "Derived_factory.hpp"

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

Nota:

  • Ho messo tutto nei file di intestazione per concisione. Nella vita reale dovresti ovviamente dividere il tuo codice tra .hppe.cpp file.
  • Per semplificare, ho ignorato il caso in cui si desidera gestire un new/ deletesovraccarico.

Due articoli chiari per ottenere maggiori dettagli:


Questo è un esempio eccellente. RAII è sicuramente la strada da percorrere.
David Steinhauer,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.