bou*_*eli 36 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() {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_ */
Run Code Online (Sandbox Code Playgroud)
然后,有一个名为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;
}
Run Code Online (Sandbox Code Playgroud)
你可以看到插件调用单例并更改单例中的属性num.
最后,有一个主要功能使用单例和插件如下:
#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);
}
Run Code Online (Sandbox Code Playgroud)
并且makefile如下:
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
Run Code Online (Sandbox Code Playgroud)
那么,输出是什么?我以为有以下几点:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
Run Code Online (Sandbox Code Playgroud)
但是,实际输出如下:
singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100
Run Code Online (Sandbox Code Playgroud)
它证明了单例类有两个实例.
为什么?
Emp*_*ian 54
首先,-fPIC在构建共享库时通常应该使用flag.
不使用它在32位Linux上"工作",但在64位上运行失败,错误类似于:
/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
Run Code Online (Sandbox Code Playgroud)
其次,在添加-rdynamic到主可执行文件的链接行后,您的程序将按预期工作:
singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101
Run Code Online (Sandbox Code Playgroud)
为了理解为什么-rdynamic需要,您需要了解动态链接器解析符号的方式以及动态符号表.
首先,让我们看一下动态符号表hello.so:
$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()
Run Code Online (Sandbox Code Playgroud)
这告诉我们有两个弱函数定义,以及一个singleton::pInstance对动态链接器可见的全局变量.
现在让我们看一下原始的静态和动态符号表example1(没有链接-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
$
Run Code Online (Sandbox Code Playgroud)
这是正确的:即使singleton::pInstance可执行文件中存在作为全局变量,该符号也不存在于动态符号表中,因此对动态链接器是"不可见的".
因为动态链接器"不知道" example1已经包含了定义singleton::pInstance,所以它不会将该变量绑定hello.so到现有定义(这是您真正想要的).
当我们添加-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()
Run Code Online (Sandbox Code Playgroud)
现在,singleton::pInstance主要可执行文件内部的定义对于动态链接器是可见的,因此在加载时它将"重用"该定义hello.so:
LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
Run Code Online (Sandbox Code Playgroud)
使用运行时加载的共享库时必须小心.这样的结构并不是C++标准的严格组成部分,你必须仔细考虑这种程序的语义是什么.
首先,正在发生的事情是共享库看到了自己独立的全局变量singleton::pInstance.这是为什么?在运行时加载的库本质上是一个独立的独立程序,恰好没有入口点.但其他一切都非常像一个单独的程序,动态加载器会像那样对待它,例如初始化全局变量等.
动态加载程序是一个与静态加载程序无关的运行时工具.静态加载器是C++标准实现的一部分,在主程序启动之前解析所有主程序的符号.另一方面,动态加载程序仅在主程序启动后运行.特别是,主程序的所有符号都必须得到解决!根本无法动态地自动替换主程序中的符号.本机程序不以任何允许系统重新链接的方式"管理".(也许有些东西可以被黑客攻击,但不是系统的,可移植的.)
所以真正的问题是如何解决你正在尝试的设计问题.这里的解决方案是将句柄传递给插件函数的所有全局变量.使主程序定义全局变量的原始(且唯一)副本,并使用指向它的指针初始化库.
例如,您的共享库可能如下所示.首先,添加指向单例类的指针:
class singleton
{
static singleton * pInstance;
public:
static singleton ** ppinstance;
// ...
};
singleton ** singleton::ppInstance(&singleton::pInstance);
Run Code Online (Sandbox Code Playgroud)
现在使用*ppInstance而不是pInstance到处.
在插件中,将单例配置为主程序中的指针:
void init(singleton ** p)
{
singleton::ppInsance = p;
}
Run Code Online (Sandbox Code Playgroud)
而主要功能,调用插件初始化:
init_fn init;
hello_fn hello;
*reinterpret_cast<void**>(&init) = dlsym(lib, "init");
*reinterpret_cast<void**>(&hello) = dlsym(lib, "hello");
init(singleton::ppInstance);
hello();
Run Code Online (Sandbox Code Playgroud)
现在,插件与程序的其余部分共享与单例实例相同的指针.