maclasse.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
maclasse.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 );
}
Sous Mac OS X, compilez avec :
g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user
Sous Linux, compilez avec :
g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user
S'il s'agissait d'un système de plug-in, vous utiliseriez MyClass comme classe de base et définiriez toutes les fonctions requises virtuellement. L'auteur du plugin dériverait alors de MyClass, remplacerait les virtuels et implémenterait create_object
et destroy_object
. Votre application principale ne devrait en aucun cas être modifiée.
L'exemple suivant montre une bibliothèque de classes partagées shared.[h,cpp] et un module main.cpp utilisant la bibliothèque. C'est un exemple très simple et le makefile pourrait être bien amélioré. Mais cela fonctionne et peut vous aider :
shared.h définit la classe :
class myclass {
int myx;
public:
myclass() { myx=0; }
void setx(int newx);
int getx();
};
shared.cpp définit les fonctions getx/setx :
#include "shared.h"
void myclass::setx(int newx) { myx = newx; }
int myclass::getx() { return myx; }
main.cpp utilise 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;
}
et le makefile qui génère libshared.so et relie main à la bibliothèque partagée :
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
Pour exécuter réellement 'main' et établir un lien avec libshared.so, vous devrez probablement spécifier le chemin de chargement (ou le mettre dans /usr/local/lib ou similaire).
Ce qui suit spécifie le répertoire actuel comme chemin de recherche des bibliothèques et exécute main (syntaxe bash) :
export LD_LIBRARY_PATH=.
./main
Pour voir que le programme est lié à libshared.so, vous pouvez essayer ldd :
LD_LIBRARY_PATH=. ldd main
Imprime sur ma machine :
~/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)
En plus des réponses précédentes, j'aimerais attirer l'attention sur le fait que vous devez utiliser l'idiome RAII (Resource Acquisition Is Initialisation) pour être sûr de la destruction du gestionnaire.
Voici un exemple de travail complet :
Déclaration d'interface :Interface.hpp
:
class Base {
public:
virtual ~Base() {}
virtual void foo() const = 0;
};
using Base_creator_t = Base *(*)();
Contenu de la bibliothèque partagée :
#include "Interface.hpp"
class Derived: public Base {
public:
void foo() const override {}
};
extern "C" {
Base * create() {
return new Derived;
}
}
Gestionnaire de bibliothèque partagée dynamique :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);
}
}
};
Code client :
#include "Derived_factory.hpp"
{
Derived_factory factory;
std::unique_ptr<Base> base = factory.create();
base->foo();
}
Remarque :
- J'ai tout mis dans des fichiers d'en-tête pour plus de concision. Dans la vraie vie, vous devriez bien sûr diviser votre code entre
.hpp
et.cpp
fichiers. - Pour simplifier, j'ai ignoré le cas où vous voulez gérer un
new
/delete
surcharge.
Deux articles clairs pour obtenir plus de détails :
- Mini tutoriel C++ dlopen
- Chargement dynamique C++ d'objets partagés lors de l'exécution