miclase.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
miclase.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;
}
usuario_clase.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 );
}
En Mac OS X, compila con:
g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user
En Linux, compila con:
g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user
Si esto fuera para un sistema de complementos, usaría MyClass como clase base y definiría todas las funciones requeridas virtuales. El autor del complemento luego se derivaría de MyClass, anularía los virtuales e implementaría create_object
y destroy_object
. No será necesario cambiar su aplicación principal de ninguna manera.
A continuación, se muestra un ejemplo de una biblioteca de clases compartida shared.[h,cpp] y un módulo main.cpp que usa la biblioteca. Es un ejemplo muy simple y el archivo MAKE se podría hacer mucho mejor. Pero funciona y puede ayudarte:
shared.h define la clase:
class myclass {
int myx;
public:
myclass() { myx=0; }
void setx(int newx);
int getx();
};
shared.cpp define las funciones getx/setx:
#include "shared.h"
void myclass::setx(int newx) { myx = newx; }
int myclass::getx() { return myx; }
main.cpp usa la clase,
#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;
}
y el makefile que genera libshared.so y vincula main con la biblioteca compartida:
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
Para ejecutar 'main' y enlazar con libshared. probablemente necesitará especificar la ruta de carga (o ponerla en /usr/local/lib o similar).
Lo siguiente especifica el directorio actual como la ruta de búsqueda de bibliotecas y ejecuta main (sintaxis bash):
export LD_LIBRARY_PATH=.
./main
Para ver que el programa está vinculado con libshared.así que puedes probar ldd:
LD_LIBRARY_PATH=. ldd main
Imprime en mi máquina:
~/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)
Además de las respuestas anteriores, me gustaría crear conciencia sobre el hecho de que debe usar el idioma RAII (Adquisición de recursos es inicialización) para estar seguro sobre la destrucción del controlador.
Aquí hay un ejemplo de trabajo completo:
Declaración de interfaz:Interface.hpp
:
class Base {
public:
virtual ~Base() {}
virtual void foo() const = 0;
};
using Base_creator_t = Base *(*)();
Contenido de la biblioteca compartida:
#include "Interface.hpp"
class Derived: public Base {
public:
void foo() const override {}
};
extern "C" {
Base * create() {
return new Derived;
}
}
Controlador de biblioteca compartida dinámica: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);
}
}
};
Código de cliente:
#include "Derived_factory.hpp"
{
Derived_factory factory;
std::unique_ptr<Base> base = factory.create();
base->foo();
}
Nota:
- Pongo todo en archivos de encabezado para que sea conciso. En la vida real, por supuesto, deberías dividir tu código entre
.hpp
y.cpp
archivos. - Para simplificar, ignoré el caso en el que desea manejar un
new
/delete
sobrecarga.
Dos artículos claros para obtener más detalles:
- C++ dlopen mini tutorial
- Carga dinámica de C++ de objetos compartidos en tiempo de ejecución