C++ Linux上跨共享库的多个singleton实例
正如标题所提到的,我的问题是显而易见的,我详细描述了这个场景。 文件singleton.h中有一个名为singleton的类,由singleton模式实现,如下所示:C++ Linux上跨共享库的多个singleton实例,c++,singleton,dlopen,C++,Singleton,Dlopen,正如标题所提到的,我的问题是显而易见的,我详细描述了这个场景。 文件singleton.h中有一个名为singleton的类,由singleton模式实现,如下所示: /* * singleton.h * * Created on: 2011-12-24 * Author: bourneli */ #ifndef SINGLETON_H_ #define SINGLETON_H_ class singleton { private: singleton() {n
/*
* 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_ */
然后,有一个名为hello.cpp的插件,如下所示:
#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;
}
#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);
}
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
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
那么,输出是什么?
我认为有以下几点:
#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;
}
#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);
}
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
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
然而,实际产出如下:
#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;
}
#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);
}
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
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
它证明了singleton类有两个实例
为什么?使用运行时加载的共享库时必须小心。这样的结构不是严格的C++标准的一部分,你必须仔细考虑这样的过程的语义是什么。 首先,发生的事情是共享库看到了它自己的、单独的全局变量
singleton::pInstance
。为什么呢?在运行时加载的库本质上是一个独立的程序,只是碰巧没有入口点。但其他一切都像是一个单独的程序,动态加载程序会这样处理,例如初始化全局变量等
动态加载程序是与静态加载程序无关的运行时工具。静态加载程序是C++标准实现的一部分,在主程序启动之前解析所有主程序的符号。另一方面,动态加载程序仅在主程序启动后运行。特别是,主程序的所有符号都必须解析!根本没有办法自动动态替换主程序中的符号。本机程序不以任何允许系统重新链接的方式进行“管理”。(也许有些东西可以被黑客入侵,但不是以系统化、便携的方式。)
所以真正的问题是如何解决您正在尝试的设计问题。这里的解决方案是将所有全局变量的句柄传递给插件函数。让主程序定义全局变量的原始(也是唯一)副本,并使用指向该副本的指针初始化库
例如,您的共享库可能如下所示。首先,添加指向singleton类指针的指针:
class singleton
{
static singleton * pInstance;
public:
static singleton ** ppinstance;
// ...
};
singleton ** singleton::ppInstance(&singleton::pInstance);
现在使用*p站姿
而不是p站姿
在插件中,将singleton配置为主程序中的指针:
void init(singleton ** p)
{
singleton::ppInsance = p;
}
和主要功能,调用插件初始化:
init_fn init;
hello_fn hello;
*reinterpret_cast<void**>(&init) = dlsym(lib, "init");
*reinterpret_cast<void**>(&hello) = dlsym(lib, "hello");
init(singleton::ppInstance);
hello();
init\u fn init;
你好;
*重新解释铸型(&init)=dlsym(lib,“init”);
*重新解释演员表(&hello)=dlsym(lib,“hello”);
init(singleton::ppInstance);
你好;
现在,插件与程序的其余部分共享指向singleton实例的相同指针。首先,在构建共享库时,通常应使用
-fPIC
标志
不使用它在32位Linux上“有效”,但在64位Linux上会失败,错误类似于:
/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
其次,将-rdynamic
添加到主可执行文件的链接行后,程序将按预期工作:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
为了理解为什么需要-rdynamic
,您需要了解动态链接器解析符号的方式以及动态符号表
首先,让我们看看hello.so的动态符号表:
$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
这告诉我们,动态链接器可以看到两个弱函数定义和一个全局变量singleton::pInstance
现在让我们看一下原始示例1的静态和动态符号表(链接时没有-rdynamic
):
没错:即使可执行文件中存在作为全局变量的singleton::pInstance
,该符号也不存在于动态符号表中,因此动态链接器“不可见”
因为动态链接器“不知道”example1
已经包含了singleton::pInstance
的定义,所以它不会将hello中的变量绑定到现有定义(这是您真正想要的)
将-rdynamic
添加到链接行时:
$ 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()
现在,主可执行文件中的singleton::pInstance
定义对动态链接器可见,因此在加载hello时它将“重用”该定义。因此
:
$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
我认为简单的答案是:
当您有一个静态变量时,它存储在对象(.o、.a和/或.so)中
如果要执行的最终对象包含该对象的两个版本,则该行为是意外的,例如,调用单例对象的析构函数
使用正确的设计,例如在主文件中声明静态成员、使用-rdynamic/fpic和使用“”编译器指令,将为您完成技巧部分
makefile语句示例:
$ g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L./Singleton/ -lsingleton -Wl,--no-whole-archive $(LIBS)
希望这能奏效 谢谢大家的回答
作为Linux的后续操作,您还可以使用RTLD_GLOBAL
和dlopen(…)
,按照man dlopen
(及其示例)。我在这个目录中制作了OP示例的一个变体:
示例输出:
又快又脏:
- 如果您不想手动将每个符号链接到您的
main
,请保留共享对象。(例如,如果您将*.so
对象导入Python)
- 您可以首先加载到全局符号表中,或者执行
NOLOAD
+global
重新打开
代码:
模式:
- 模式0:标称延迟加载(不起作用)
- 模式1:包含要添加到静态符号表的文件
- 模式2:最初使用RTLD_全局加载
- 模式3:使用RTLD_空载| RTLD_全局重新加载
是否希望此单例成为正在执行的一个进程的单例?或者一个“系统范围”的单例违反了所有受保护内存提供给我们的?问题,除非明确说明