c++ singleton dlopen

c++ - Varias instancias de singleton en bibliotecas compartidas en Linux



dlopen (4)

Mi pregunta, como el título mencionado, es obvia, y describo el escenario en detalle. Hay una clase llamada singleton implementada por el patrón singleton como sigue, en el archivo singleton.h:

/* * singleton.h * * Created on: 2011-12-24 * Author: bourneli */ #ifndef SINGLETON_H_ #define SINGLETON_H_ class singleton { private: singleton() {num = -1;} static singleton* pInstance; public: static singleton& instance() { if (NULL == pInstance) { pInstance = new singleton(); } return *pInstance; } public: int num; }; singleton* singleton::pInstance = NULL; #endif /* SINGLETON_H_ */

luego, hay un complemento llamado hello.cpp de la siguiente manera:

#include <iostream> #include "singleton.h" extern "C" void hello() { std::cout << "singleton.num in hello.so : " << singleton::instance().num << std::endl; ++singleton::instance().num; std::cout << "singleton.num in hello.so after ++ : " << singleton::instance().num << std::endl; }

puede ver que el complemento llama al singleton y cambia el número de atributo en el singleton.

Por último, hay una función principal que utiliza el singleton y el complemento de la siguiente manera:

#include <iostream> #include <dlfcn.h> #include "singleton.h" int main() { using std::cout; using std::cerr; using std::endl; singleton::instance().num = 100; // call singleton cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton // open the library void* handle = dlopen("./hello.so", RTLD_LAZY); if (!handle) { cerr << "Cannot open library: " << dlerror() << ''/n''; return 1; } // load the symbol typedef void (*hello_t)(); // reset errors dlerror(); hello_t hello = (hello_t) dlsym(handle, "hello"); const char *dlsym_error = dlerror(); if (dlsym_error) { cerr << "Cannot load symbol ''hello'': " << dlerror() << ''/n''; dlclose(handle); return 1; } hello(); // call plugin function hello cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton dlclose(handle); }

y el makefile esta siguiendo:

example1: main.cpp hello.so $(CXX) $(CXXFLAGS) -o example1 main.cpp -ldl hello.so: hello.cpp $(CXX) $(CXXFLAGS) -shared -o hello.so hello.cpp clean: rm -f example1 hello.so .PHONY: clean

Entonces, ¿cuál es la salida? Pensé que hay seguidores:

singleton.num in main : 100 singleton.num in hello.so : 100 singleton.num in hello.so after ++ : 101 singleton.num in main : 101

sin embargo, la salida real es la siguiente:

singleton.num in main : 100 singleton.num in hello.so : -1 singleton.num in hello.so after ++ : 0 singleton.num in main : 100

Demuestra que hay dos instancias de la clase singleton.

¿Por qué?


¡Gracias a todos por sus respuestas o comentarios!

Como seguimiento para Linux, también puede usar RTLD_GLOBAL con dlopen(...) , por man dlopen (y los ejemplos que tiene). He hecho una variante del ejemplo de OP en este directorio: github tree Ejemplo de salida: output.txt

Rápido y sucio:

  • Si no desea tener que vincular manualmente cada símbolo a su main , mantenga los objetos compartidos alrededor. (por ejemplo, si creó *.so objetos para importar a Python)
  • Inicialmente puede cargar en la tabla de símbolos global, o hacer una NOLOAD + GLOBAL .

Código:

#if MODE == 1 // Add to static symbol table. #include "producer.h" #endif ... #if MODE == 0 || MODE == 1 handle = dlopen(lib, RTLD_LAZY); #elif MODE == 2 handle = dlopen(lib, RTLD_LAZY | RTLD_GLOBAL); #elif MODE == 3 handle = dlopen(lib, RTLD_LAZY); handle = dlopen(lib, RTLD_LAZY | RTLD_NOLOAD | RTLD_GLOBAL); #endif

Modos:

  • Modo 0: carga perezosa nominal (no funcionará)
  • Modo 1: Incluir archivo para agregar a la tabla de símbolos estáticos.
  • Modo 2: Cargar inicialmente usando RTLD_GLOBAL
  • Modo 3: recargar utilizando RTLD_NOLOAD | RTLD_GLOBAL

Creo que la respuesta simple está aquí: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

Cuando tienes una variable estática, se almacena en el objeto (.o, .a y / o .so)

Si el objeto final que se ejecutará contiene dos versiones del objeto, el comportamiento es inesperado, como por ejemplo, llamar al Destructor de un objeto Singleton.

El uso del diseño adecuado, como declarar el miembro estático en el archivo principal y el uso de las etiquetas -rdynamic / fpic y "" compilador, hará el truco por usted.

Ejemplo de declaración makefile:

$ g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L./Singleton/ -lsingleton -Wl,--no-whole-archive $(LIBS)

¡Espero que esto funcione!


Debe tener cuidado al usar bibliotecas compartidas cargadas en tiempo de ejecución. Dicha construcción no es estrictamente parte del estándar C ++, y debe considerar cuidadosamente lo que resulta ser la semántica de tal procedimiento.

En primer lugar, lo que sucede es que la biblioteca compartida ve su propia variable global separada singleton::pInstance . ¿Porqué es eso? Una biblioteca que se carga en tiempo de ejecución es esencialmente un programa separado e independiente que simplemente no tiene un punto de entrada. Pero todo lo demás es realmente como un programa separado, y el cargador dinámico lo tratará así, por ejemplo, inicializar variables globales, etc.

El cargador dinámico es una función de tiempo de ejecución que no tiene nada que ver con el cargador estático. El cargador estático es parte de la implementación estándar de C ++ y resuelve todos los símbolos del programa principal antes de que comience el programa principal. El cargador dinámico, por otro lado, solo se ejecuta después de que el programa principal ya haya comenzado. ¡En particular, todos los símbolos del programa principal ya deben ser resueltos! Simplemente no hay manera de reemplazar dinámicamente los símbolos del programa principal. Los programas nativos no se "administran" de ninguna manera que permita una reenlazada sistemática. (Tal vez algo puede ser pirateado, pero no de manera sistemática y portátil).

Entonces, la verdadera pregunta es cómo resolver el problema de diseño que estás intentando. La solución aquí es pasar identificadores a todas las variables globales a las funciones del complemento. Haga que su programa principal defina la copia original (y única) de la variable global, y que inicialice su biblioteca con un puntero a eso.

Por ejemplo, su biblioteca compartida podría verse así. Primero, agregue un puntero a puntero a la clase singleton:

class singleton { static singleton * pInstance; public: static singleton ** ppinstance; // ... }; singleton ** singleton::ppInstance(&singleton::pInstance);

Ahora use *ppInstance lugar de pInstance todas partes.

En el complemento, configure el singleton al puntero desde el programa principal:

void init(singleton ** p) { singleton::ppInsance = p; }

Y la función principal, llame a la inicialización del complemento:

init_fn init; hello_fn hello; *reinterpret_cast<void**>(&init) = dlsym(lib, "init"); *reinterpret_cast<void**>(&hello) = dlsym(lib, "hello"); init(singleton::ppInstance); hello();

Ahora el complemento comparte el mismo puntero a la instancia de singleton que el resto del programa.


En primer lugar, generalmente debe utilizar el indicador -fPIC al -fPIC bibliotecas compartidas.

No usarlo "funciona" en Linux de 32 bits, pero fallaría en uno de 64 bits con un error similar al siguiente:

/usr/bin/ld: /tmp/ccUUrz9c.o: relocation R_X86_64_32 against `.rodata'' can not be used when making a shared object; recompile with -fPIC

En segundo lugar, su programa funcionará como espera después de agregar -rdynamic a la línea de enlace para el ejecutable principal:

singleton.num in main : 100 singleton.num in hello.so : 100 singleton.num in hello.so after ++ : 101 singleton.num in main : 101

Para comprender por -rdynamic se requiere -rdynamic , debe conocer la forma en que el enlazador dinámico resuelve los símbolos y la tabla de símbolos dinámicos .

Primero, veamos la tabla de símbolos dinámicos para hello.so :

$ nm -C -D hello.so | grep singleton 0000000000000b8c W singleton::instance() 0000000000201068 B singleton::pInstance 0000000000000b78 W singleton::singleton()

Esto nos dice que hay dos definiciones de funciones débiles y una variable global singleton::pInstance que son visibles para el enlazador dinámico.

Ahora veamos la tabla de símbolos estáticos y dinámicos para el example1 original1 (vinculado sin -rdynamic ):

$ nm -C example1 | grep singleton 0000000000400d0f t global constructors keyed to singleton::pInstance 0000000000400d38 W singleton::instance() 00000000006022e0 B singleton::pInstance 0000000000400d24 W singleton::singleton() $ nm -C -D example1 | grep singleton $

Así es: aunque singleton::pInstance está presente en el ejecutable como una variable global, ese símbolo no está presente en la tabla de símbolos dinámicos y, por lo tanto, es "invisible" para el vinculador dinámico.

Debido a que el enlazador dinámico "no sabe" que example1 ya contiene una definición de singleton::pInstance , no vincula esa variable dentro de hello.so a la definición existente (que es lo que realmente desea).

Cuando agregamos -rdynamic a la línea de enlace:

$ nm -C example1-rdynamic | grep singleton 0000000000400fdf t global constructors keyed to singleton::pInstance 0000000000401008 W singleton::instance() 00000000006022e0 B singleton::pInstance 0000000000400ff4 W singleton::singleton() $ nm -C -D example1-rdynamic | grep singleton 0000000000401008 W singleton::instance() 00000000006022e0 B singleton::pInstance 0000000000400ff4 W singleton::singleton()

Ahora la definición de singleton::pInstance dentro del ejecutable principal es visible para el enlazador dinámico, por lo que "reutilizará" esa definición al cargar hello.so :

LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance 31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE''