Ma question, comme le titre l'indique, est évidente et je décris le scénario en détail. Il existe une classe nommée singleton implémentée par singleton pattern comme suit, dans le fichier 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_ */
ensuite, il existe un plugin appelé hello.cpp comme suit:
#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;
}
vous pouvez voir que le plugin appelle le singleton et change l'attribut num dans le singleton.
enfin, il existe une fonction principale qui utilise le singleton et le plugin comme suit:
#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);
}
et le makefile est le suivant:
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
alors, quelle est la sortie? Je pensais qu'il y avait:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
cependant, la sortie réelle est la suivante:
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
Cela prouve qu'il existe deux instances de la classe singleton.
Pourquoi?
Tout d'abord, vous devez généralement utiliser l'indicateur -fPIC
lors de la création de bibliothèques partagées.
Ne l'utilisez pas "fonctionne" sous Linux 32 bits, mais échouera sous 64 bits avec une erreur similaire à:
/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
Deuxièmement, votre programme fonctionnera comme prévu après avoir ajouté -rdynamic
à la ligne de liaison pour le fichier exécutable principal:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
Afin de comprendre pourquoi -rdynamic
est requis, vous devez connaître la façon dont l’éditeur de liens dynamique résout les symboles et la table dynamic symbol.
Commençons par examiner la table des symboles dynamiques pour hello.so
:
$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()
Cela nous indique qu'il existe deux définitions de fonction faibles et une variable globale singleton::pInstance
visibles par l'éditeur de liens dynamique.
Examinons maintenant la table des symboles statiques et dynamiques pour le example1
d'origine (lié sans -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
$
C’est vrai: même si le singleton::pInstance
est présent dans l’exécutable en tant que variable globale, ce symbole n’est pas présent dans la table dynamic symbol et est donc "invisible" pour l’éditeur de liens dynamique.
Parce que l'éditeur de liens dynamique "ne sait pas" que example1
contient déjà une définition de singleton::pInstance
, il ne lie pas cette variable à l'intérieur de hello.so
à la définition existante (ce que vous voulez réellement).
Lorsque nous ajoutons -rdynamic
à la ligne de lien:
$ 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()
Désormais, la définition de singleton::pInstance
à l'intérieur du fichier exécutable principal est visible pour l'éditeur de liens dynamique. Elle sera donc "réutilisée" lors du chargement de hello.so
:
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
Vous devez faire attention lorsque vous utilisez des bibliothèques partagées chargées à l'exécution. Une telle construction ne fait pas strictement partie de la norme C++ et vous devez examiner avec soin la sémantique d'une telle procédure.
Premièrement, la bibliothèque partagée voit sa propre variable globale séparée singleton::pInstance
. Pourquoi donc? Une bibliothèque chargée au moment de l'exécution est essentiellement un programme distinct et indépendant qui n'a tout simplement pas de point d'entrée. Mais tout le reste est vraiment comme un programme séparé, et le chargeur dynamique le traitera comme ça, par exemple. initialiser les variables globales, etc.
Le chargeur dynamique est une installation d'exécution qui n'a rien à voir avec le chargeur statique. Le chargeur statique fait partie de l'implémentation standard C++ et résout tous les symboles du programme principal avant le programme principal démarre. D'autre part, le chargeur dynamique n'exécute que après le programme principal a déjà démarré. En particulier, tous les symboles du programme principal doivent déjà être résolus! Il existe simplement non moyen de remplacer automatiquement les symboles du programme principal de manière dynamique. Les programmes natifs ne sont en aucun cas "gérés" de manière à permettre la création de liens systématiques. (Peut-être que quelque chose peut être piraté, mais pas de manière systématique et portable.)
La vraie question est donc de savoir comment résoudre le problème de conception que vous tentez. La solution ici est de transmettre les descripteurs de toutes les variables globales aux fonctions du plug-in. Faites en sorte que votre programme principal définisse la copie originale (et unique) de la variable globale et initialise votre bibliothèque avec un pointeur sur celle-ci.
Par exemple, votre bibliothèque partagée pourrait ressembler à ceci. Tout d'abord, ajoutez un pointeur à pointeur à la classe singleton:
class singleton
{
static singleton * pInstance;
public:
static singleton ** ppinstance;
// ...
};
singleton ** singleton::ppInstance(&singleton::pInstance);
Maintenant, utilisez *ppInstance
au lieu de pInstance
partout.
Dans le plugin, configurez le singleton sur le pointeur du programme principal:
void init(singleton ** p)
{
singleton::ppInsance = p;
}
Et la fonction principale, appelez l’initialisation du plugin:
init_fn init;
hello_fn hello;
*reinterpret_cast<void**>(&init) = dlsym(lib, "init");
*reinterpret_cast<void**>(&hello) = dlsym(lib, "hello");
init(singleton::ppInstance);
hello();
Maintenant, le plugin partage le même pointeur sur l'instance singleton que le reste du programme.
Je pense que la réponse simple est la suivante: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
Quand vous avez une variable statique, elle est stockée dans l'objet (.o, .a et/ou .so)
Si le dernier objet à exécuter contient deux versions de l'objet, le comportement est inattendu, comme par exemple l'appel du destructeur d'un objet Singleton.
Utiliser la conception appropriée, telle que déclarer le membre statique dans le fichier principal, utiliser -rdynamic/fpic et utiliser les directives "" du compilateur, fera l'affaire pour vous.
Exemple d'instruction makefile:
$ g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L./Singleton/ -lsingleton -Wl,--no-whole-archive $(LIBS)
J'espère que ça marche!
Merci à tous pour vos réponses!
Pour le suivi de Linux, vous pouvez également utiliser RTLD_GLOBAL
avec dlopen(...)
, par man dlopen
(et les exemples dont il dispose). J'ai créé une variante de l'exemple de l'OP dans ce répertoire: github tree Exemple de sortie: output.txt
Rapide et sale:
main
, conservez les objets partagés. (par exemple, si vous avez créé des objets *.so
à importer dans Python)NOLOAD
+ GLOBAL
.Code:
#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
Modes: