2011-12-24 134 views
28

我的问题,如上所述,显而易见,我将详细描述该方案。 有一个名为单用Singleton模式实现为下面的类,在文件singleton.h:在Linux上的共享库上存在多个单例实例

/* 
* 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; 
} 

你可以看到该插件调用单例并更改单例中的属性num。

最后,还有一个主要功能使用Singleton和插件如下:

#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 

事实证明,有单件类的两个实例。

为什么?

+0

你想让这个singleton成为你正在执行的一个进程的单例吗?还是一个违反所有受保护内存的“全系统”单身人士为我们提供的? – sarnold 2011-12-24 09:06:44

+0

除非明确说明,否则问题通常不是很明显。你想共享库共享单例吗?你理论中的任何一种行为还是实际体验它?没有办法知道,除非你告诉我们。 – 9000 2011-12-24 09:07:19

+0

@sarnold:有一个众所周知的全系统单例模式,不受地址空间限制:它被称为服务器。但直到原始海报告诉他的代码的_purpose_,很难说这个模式是否合适。 – 9000 2011-12-24 09:12:11

回答

45

首先,建立共享库时一般应该使用-fPIC标志。

不使用它在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 

其次,你希望你添加-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,你需要了解的动态链接器解析符号的方式,以及有关动态个符号l表。

首先,让我们来看看在动态符号表hello.so

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

这告诉我们,有两个弱函数定义,和一个全局变量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 
$ 

这是正确的:即使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() 

现在主要的可执行文件内的singleton::pInstance定义是可见到动态连接器,所以加载hello.so何时会“重用”这个定义:

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

-rdynmaic选项解决了这个问题,谢谢。我感谢你的帮助:) – bourneli 2012-01-24 13:41:54

+1

@BourneLi如果答案适合你,你应该接受它。 – 2012-01-24 16:00:49

+0

你能解释一下如何做相反的事吗?我有一个共享的基础库,两个插件链接到,共享库导出一个单身人士;但我希望每个插件都保留它自己的单身副本。我没有将-rdynamic列为编译标志。 – 2016-11-17 14:56:47

4

使用运行时加载的共享库时必须小心。这样的构造并不完全是C++标准的一部分,你必须仔细考虑这个过程的语义是什么。

首先,共享库会看到它自己的独立全局变量singleton::pInstance。这是为什么?在运行时加载的库本质上是一个独立的独立程序,恰好没有入口点。但其他一切都像是一个单独的程序,动态加载器会像这样对待它,例如初始化全局变量等。

动态加载器是一个运行时工具,与静态加载器无关。静态加载器是C++标准实现的一部分,并且在主程序启动之前,解决了之前的所有主程序符号。另一方面,动态加载程序仅在主程序已启动后才运行。特别是,主程序的所有符号已经得到解决!有简单的方法来动态地自动替换主程序中的符号。原生程序不能以允许系统重新链接的任何方式进行“管理”。 (也许有些东西可能会被黑客入侵,但不是以系统的便携方式。)

所以真正的问题是如何解决您尝试的设计问题。这里的解决方案是将句柄传递给所有全局变量以实现插件功能。让你的主程序定义全局变量的原始(唯一)副本,并用指向该库的指针初始化你的库。

例如,您的共享库可能如下所示。首先,一个指针到指针添加到单例类:

class singleton 
{ 
    static singleton * pInstance; 
public: 
    static singleton ** ppinstance; 
    // ... 
}; 

singleton ** singleton::ppInstance(&singleton::pInstance); 

现在使用*ppInstance代替pInstance无处不在。

在插件,配置单从主程序指针:

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

和主要功能,调用插件intialization:

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

init(singleton::ppInstance); 
hello(); 

现在插件共享相同的指针作为程序的其余部分到单身实例。

+2

如果您强制使用某个全局地址初始化“singleton”,那么它不再是* singleton *。你的回答在许多细节上是不正确的,你提出的解决方案是(恕我直言)伪造。 – 2011-12-24 20:45:28

+0

我同意。采用这种解决方案会失去使用单例模式的全部目的。 – volpato 2015-03-02 19:57:06

2

我认为简单的答案就在这里: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html

当你有一个静态变量,它存储在目标(.o,u和/或.so)

如果要执行的最终对象包含对象的两个版本,行为不正常例如,调用Singleton对象的析构函数。

使用正确的设计,例如在主文件中声明静态成员并使用-rdynamic/fpic并使用“”编译器指令将为您制作技巧部分。

的makefile例子声明:

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

希望这个作品!

0

谢谢大家的回答!

作为Linux的后续工作,您还可以使用RTLD_GLOBALdlopen(...),根据man dlopen(以及它的示例)。我做了OP的例子的变体在这个目录中:github tree 输出示例:output.txt

快速和肮脏的:

  • 如果你不希望有在每个符号上手动链接你的main,保持共享对象。 (例如,如果您将*.so对象导入到Python中)
  • 您可以初始加载到全局符号表中,或者执行NOLOAD + GLOBAL重新打开。

代码:

#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 

模式:

  • 模式0:标称延迟加载(行不通)
  • 模式1:包括文件,加入到静态符号表。
  • 模式2:初始使用RTLD_GLOBAL加载
  • 模式3:使用RTLD_NOLOAD重新加载| RTLD_GLOBAL