2011-12-24 85 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