2013-03-28 21 views
1

假設我們使用gcc/g ++和隨機委員會指定的C API。本規範定義了功能在C++中包裝不同的C API實現

void foo(void); 

現在,根據此規範有幾種實現。我們選取兩個作爲樣本,並將它們分別稱爲nfooxfoo(分別由libnfoo和libxfoo分別提供爲靜態和動態庫)。

現在,我們要爲foo -API創建一個C++框架。因此,我們指定一個抽象類

class Foo 
{ 
    public: 
    virtual void foo(void) = 0; 
}; 

和相應實現

#include <nfoo.h> 
#include "Foo.h" 

class NFoo : public Foo 
{ 
    public: 
    virtual void foo(void) 
    { 
     ::foo(); // calling foo from the nfoo C-API 
    } 
}; 

以及

#include <xfoo.h> 
#include "Foo.h" 

class XFoo : public Foo 
{ 
    public: 
    virtual void foo(void) 
    { 
     ::foo(); // calling foo from the xfoo C-API 
    } 
}; 

現在,我們正面臨着一個問題:我們如何創建(即鏈接)一切都變成一個庫?

我看到將有一個符號與C API實現的foo函數符號衝突。

我已經嘗試到C++包裝實現分成單獨的靜態庫,但後來我意識到(再次),該靜態庫僅僅是一個未連接的對象文件的集合。所以這根本不起作用,除非有一種方法可以將C庫完全鏈接到包裝器中並移除/隱藏它們的符號。

建議非常感謝。

更新:最優的解決方案應該同時支持這兩種實現方式。

注意:該代碼並不意味着功能。感知它作爲僞代碼。

+0

在C或C++中沒有任何東西可以幫助您實現這一點。無論如何,衝突符號都不能連接在一起。您可以動態加載這些庫,也可以用一塊大石頭錘住它們以消除衝突(有些工具可以重命名對象文件中的符號,用於* some *目標文件格式)。 –

回答

1

第一件事,第一,如果你要在C++代碼中結束了一個C API,你應該隱藏compilation firewall背後的依賴。這是爲了(1)避免使用來自C API的名稱來污染全局名稱空間,並且(2)將用戶代碼從依賴關係釋放到第三方頭部。在這個例子中,可以做一個相當微不足道的修改來將依賴關係分離到C API。你應該這樣做:

// In NFoo.h: 

#include "Foo.h" 

class NFoo : public Foo 
{ 
    public: 
    virtual void foo(void); 
}; 

// In NFoo.cpp: 

#include "NFoo.h" 

#include <nfoo.h> 

void NFoo::foo(void) { 
    ::foo(); // calling foo from the nfoo C-API 
} 

上面的一點是,C API頭,<nfoo.h>,僅包含在cpp文件,而不是在頭文件。這意味着用戶代碼不需要提供C API頭文件來編譯使用您的庫的代碼,C API中的全局命名空間名稱也不會與其他正在編譯的其他代碼發生衝突。另外,如果你的C API(或者任何其他的外部依賴)在使用API​​的時候需要創建一些東西(例如句柄,對象等),那麼你也可以把它們包裝在一個PImpl中(指向只在cpp文件中聲明定義的前向聲明的實現類)以實現對外部依賴關係(即「編譯防火牆」)的相同隔離。

現在,基本的東西是出的方式,我們可以在手移動到這個問題:同時連接兩個不同的C API的使用名稱撞擊的符號。這是一個問題,並沒有簡單的解決方法。上述編譯防火牆技術實際上是關於在編譯隔離和減少依賴,並通過這一點,你可以很容易地編譯代碼依賴於兩個API與衝突的名字(然而這是不是真的在你的版本),你在達到鏈接階段時仍會受到ODR(一個定義規則)錯誤的嚴重打擊。

This thread有一些有用的技巧,以解決C API名稱衝突。總之,您有以下選擇:

  • 如果你有機會到靜態庫(或目標文件)的兩個C的API中的至少一個,那麼你可以使用一個工具一樣objcopy(在unix/Linux)爲該靜態庫中的所有符號(目標文件)添加前綴,例如,使用命令objcopy --prefix-symbols=libn_ libn.o將所有符號與libn.o中的所有符號作爲libn_的前綴。當然,這意味着您需要爲API頭文件中的聲明添加相同的前綴(或者僅使用您需要的縮減版本),但從維護角度來看這不是問題因爲你有一個適當的編譯防火牆來實現這種外部依賴。

  • 如果您無法訪問靜態庫(或對象文件)或者不想執行上述操作(有點麻煩),則必須使用動態庫。然而,這聽起來並不像聽起來那麼微不足道(我甚至不會去討論DLL Hell這個話題)。您必須使用動態鏈接庫(或共享對象文件)的動態加載,而不是更常見的靜態加載。也就是說,您必須使用LoadLibrary/GetProcAddress/FreeLibrary(用於Windows)和dlopen/dlsym/dlclose(所有類Unix操作系統)。這意味着您必須單獨加載並設置您希望使用的每個函數的函數指針地址。同樣,如果代碼中的依賴關係被恰當地隔離,這將只是編寫所有這些重複代碼的問題,但這裏涉及的危險並不大。如果您對C API的使用比C API本身簡單得多(例如,您只使用幾百個函數中的幾個函數),那麼創建兩個動態庫可能會容易得多,每個C API一個,只導出有限的函數子集,給它們賦予唯一的名稱,將C API調用打包。然後,您的主應用程序或庫可以直接鏈接到這兩個動態庫(靜態加載)。當然,如果你需要爲C API中的所有功能做到這一點,那麼解決所有這些問題就毫無意義。

所以,你可以選擇什麼似乎更合理或您是可行的,但毫無疑問,這將需要相當多的手工作業來解決這個問題了。

+0

非常感謝您在此處介紹的廣泛觀點。我現在開始了第二種方法,但是因爲我不知道'objcopy'工具,所以我也會仔細研究一下。再次感謝您的好評。 –

0

如果你只是想在同一時間訪問一個庫實現,以自然的方式去了解它是作爲一個動態庫

Windows中也適用於同時接入兩個或多個庫的實現,因爲Windows動態庫提供力所能及的總封裝裏面

+0

謝謝,有關Windows聲音的信息很有趣,因爲可移植性是我面臨的另一個問題。但現在我專注於Linux上的這個問題。 –

0

IIUC ifdef是你所需要的

#define _NFOO 在nfoo lib和#define XFOO在xfoo庫。

還記得,如果nfoo lib和xfoo LIB都有一個叫Foo的話,會有編譯時錯誤的功能。爲了避免這種情況,GCC/G ++通過名稱修改使用函數重載。

,那麼你可以檢查是否xfoo使用的ifdef

#ifdef XFOO 
//call xfoo's foo() 
#endif 
+0

謝謝,但我希望這兩個實現在運行時可用。有些東西像調整它的選項'Foo * f = a_runtime_option?新的NFoo():新的XFoo();'。 –

+0

爲什麼不能創建兩個lib xfoo.so和nfoo.so並在C++中使用它們 – rkm

0

接頭可以不相​​同的符號名稱的兩個不同的定義區分的聯繫,因此,如果你想用兩個函數具有相同的名稱你必須以某種方式將它們分開。

將它們分開的方法是將它們放入動態庫中。您可以選擇從動態庫中導出哪些內容,因此您可以在隱藏底層API函數的同時導出包裝。您也可以在運行時加載動態庫並一次綁定一個符號,因此即使同一個名稱在多個符號中定義,它們也不會互相干擾。

2

您可以在運行時使用dlopen/dlsym來解決您的foo調用。

類似例子代碼鏈接(可能無法編譯):

void *handle,*handle2; 
void (*fnfoo)() = null; 
void (*fxfoo)() = null; 


/* open the needed object */ 
handle = dlopen("/usr/home/me/libnfoo.so", RTLD_LOCAL | RTLD_LAZY); 
handle2 = dlopen("/usr/home/me/libxfoo.so", RTLD_LOCAL | RTLD_LAZY); 

fnfoo = dlsym(handle, "foo"); 
fxfoo = dlsym(handle, "foo"); 


/* invoke function */ 
(*fnfoo)(); 
(*fxfoo)(); 

//不要忘記dlclose()的

否則,在庫中的符號將需要被修改。 這不能移植到windows。

+0

謝謝你的提示。我也接觸到這種方法。 –