2013-06-12 30 views
2

我有一個程序,靜態與出口的一些功能幾個C++庫的鏈接被刪除:保持導出函數由鏈接器

extern "C" 
{ 
    KSrvRequestHandler* CreateRequestHandler(const char* name); 
    bool    DestroyRequestHandler(KSrvRequestHandler* handler); 
    const char**  ListRequestHandlerTypes(); 
} 

主程序然後調用使用GetProcAddress的/ dlsym進行這些功能:

#ifdef WIN32 

    HINSTANCE hDll = GetModuleHandle(NULL); 

    mCreateHandler = GetProcAddress(hDll, createFuncName ); 
    mDestroyHandler = GetProcAddress(hDll, destroyFuncName); 
    mGetHandlerTypes = GetProcAddress(hDll, listFuncName ); 

#else // POSIX 

    void* handle = dlopen(NULL, 0); 

    mCreateHandler = dlsym(handle, createFuncName ); 
    mDestroyHandler = dlsym(handle, destroyFuncName); 
    mGetHandlerTypes = dlsym(handle, listFuncName ); 
    dlclose(handle); 

#endif // !POSIX 

所以這裏的關鍵是我使用動態鏈接在我自己的主程序中調用一個函數。 (爲什麼我這樣做超出了問題的範圍,但簡短的回答:這是一個插件架構,但我有一些標準的插件直接鏈接到主要的二進制文件 - 但我仍然想通過它來加載它們同樣的插件加載界面。例如,對於內置插件,我通過傳入當前可執行文件作爲插件接口的源代碼來加載它們。)

這裏是問題:鏈接器不知道我要去需要這些功能,並且不會鏈接它們。

如何強制這些函數被鏈接?對於動態庫,導出它們就足夠了。但是對於一個exe文件,甚至dll導出的函數都會被鏈接器刪除。

我知道我可以強制鏈接,讓主要的二進制文件將這些函數地址分配給某些東西或其他類似的黑客。有沒有正確的方法來做到這一點?

@UPDATE:所以我有一個解決方案,但它確實是醜陋的內部。仍在尋找更好的方法。

所以我必須以某種方式在加載內置接口的對象中定義我需要的符號。我不認爲有強制鏈接器鏈接符號的方法。例如。我無法知道如何構建一個函數庫,該函數始終與其看起來需要或不需要的函數相關聯。這完全取決於可執行文件的鏈接步驟。

所以在可執行文件中,我有一個宏定義了我需要的內置接口。每個內置插件有一個前綴,其所有的接口函數,因此,在我做文件的頂部:

DEFINE_BUILT_IN_PLUGIN(PluginOne) 
DEFINE_BUILT_IN_PLUGIN(PluginTwo) 

這將迫使我所需要的功能的定義。但宏做,這是這麼醜,我充滿了憤怒和自我懷疑的感覺(我已經刪除了尾隨從宏觀的可讀性斜槓):

#define FORCE_UNDEFINED_SYMBOL(x) 
    void* _fp_ ## x ## _fp =(void*)&x; 
    if (((ptrv) _fp_ ## x ##_fp * (rand() | 1)) < 1) 
     exit(0); 

#define DEFINE_BUILT_IN_PLUGIN(PREFIX) 

extern "C" 
{                         
    KSrvRequestHandler* PREFIX ## CreateRequestHandler(const char* name); 
    bool    PREFIX ## DestroyRequestHandler(KSrvRequestHandler* handler);  
    const char**  PREFIX ## ListRequestHandlerTypes(); 
} 

class PREFIX ## HandlerInterfaceMagic 
{  
public: 
    PREFIX ## HandlerInterfaceMagic() 
    { 
     FORCE_UNDEFINED_SYMBOL(PREFIX ## CreateRequestHandler); 
     FORCE_UNDEFINED_SYMBOL(PREFIX ## DestroyRequestHandler); 
     FORCE_UNDEFINED_SYMBOL(PREFIX ## ListRequestHandlerTypes); 
    } 
};    
PREFIX ## HandlerInterfaceMagic PREFIX ## HandlerInterfaceMagicInstance; 

由於編譯器是一種優化的天才, ,在FORCE_UNDEFINED_SYMBOLS我會竭盡全力欺騙編譯器鏈接一個未引用的函數。該宏只適用於某個功能。所以我必須創造這個假魔術課。一定會有更好的辦法。

無論如何 - 它確實有效。

+0

我可能會誤解你的情況,但是你可以將導出的函數移動到單獨編譯的庫中,然後在需要時將它們從庫中鏈接起來? –

+0

如果我將內置的插件插入到它們自己的動態庫中,那麼這就行得通了 - 基本上只是將它們編寫爲常規插件,而且它們總是附帶可執行文件。但是這不是最理想的,因爲我希望他們都在同一個文件中。但更大的問題是,內置插件需要訪問主程序的內部。例如。其中一個是內部程序狀態報告的診斷插件。我必須創建一個完整的反向界面(例如exe->插件),以將diag插件作爲單獨的dyn來完成此操作。庫。 –

+0

請不要使用符號名稱的前導雙下劃線,它們是爲編譯器保留的。如果您擔心名稱衝突,請將它們放在名稱空間中。 –

回答

2

我已經看到至少有兩種不同的方法來解決類似的任務。

  1. 在Qt例如,你可以有靜態的插件,它需要通過調用特定的宏被「進口」到主可執行文件:

    https://qt-project.org/doc/qt-4.8/qtplugin.html#Q_IMPORT_PLUGIN

    它創建一個靜態實例自定義類的構造函數調用從靜態插件導出的初始化函數。

  2. 的波科傢伙強制特定符號從靜態庫導出在Linux上使用的extern "C"聲明,並在Windows上編譯:

    __pragma(comment (linker, "/export:CreateRequestHandler"))

    到靜態庫聯動被迫與同樣extern "C"聲明在Linux和Windows上的接頭編譯:

    __pragma(comment (linker, "/include:CreateRequestHandler"))

    你可以在這個blog post找到詳細信息。

+0

感謝您的支持。 Qt解決這個問題的方式類似於我在我自己非常醜陋的導入宏中所做的解釋。 –

+0

是的,但如果我正確理解你的情況,你的外部「C」功能就是一個虛擬。在Qt中,實際需要調用函數(在導入可執行文件或共享庫的靜態初始化過程中)並註冊靜態插件。 – Sascha

+0

似乎在QT示例中,QT向插件註冊一個註冊api,並且在靜態init時調用它們來讓插件自行安裝。在我的情況下,系統加載配置文件中配置的插件。所以這個系統是在控制或不是插件的負載。所以它需要的只是插件功能可以在插件動態庫中使用,或者編譯到主系統中。我現在通過使用函數指針地址來強制它們被包含來強制鏈接。如果優化器變得更聰明,它將停止再次鏈接它們。 –

0

你不能提供一個.def文件到你的主要可執行文件鏈接器嗎?該文件應導出有問題的功能,這將防止它們被刪除。

我似乎記得我很久以前就做過這樣的事情。

0

問題:在windows上,STATIC LIB包含一個帶有標記爲__decl-spec(dll-export)功能的OBJ文件,但如果在EXE中未使用相同的功能,則函數不會從EXE導出。在其他平臺上,我們也遇到了同樣的問題,但是我們有編譯選項,例如--whole-archive/-force_load,確實能夠工作。

鏈接:Link1Link2

只有解決方案,來我的心是不是創建靜態庫,而包括在可執行的所有代碼(靜態LIBS),則:1,適用於Windows 2.它適用於沒有--whole-archive的Linux 3.它在Mac OS X上無需使用-force_load 4.我們也不用擔心是否包含死碼,exe膨脹等。

這是唯一的解決方案,直到連接器變得聰明並丟棄每一個未使用的符號,除了特別標記爲外部消費的那些符號,即標記爲要出口的符號。