2012-08-13 78 views
6

我們有一個很大的舊C++應用程序,其中包含大量遺留代碼和一些用C語言編寫的外部庫。這些庫很少更新 - 只有當我們發現一個錯誤和供應商提供補丁。這發生在上週的一個圖書館,並在整合新版本後,我們發現如果我們不在本地修改圖書館(我們顯然是用最後一個版本做的),我們的版本會打破這個錯誤信息:在C++中處理C庫匿名結構類型

non-local function ‘static E* MyCls::myFct(<anonymous struct>*)’ uses anonymous type 

這是由於圖書館宣佈了一些手柄類型是這樣的:

#define _Opaque struct {unsigned long x;} * 

typedef _Opaque Handle; 
typedef _Opaque Request; 

這是我們在一些班函數簽名就在我們身邊使用:

class MyCls { 
public: 
    static void* myFct(Handle handle); 
    ... 
} 

由於_Opaque結構沒有名稱,編譯器無法爲函數創建合適的名稱 - 名稱,這會產生上述錯誤。

我們對這個當前的解決方法是打補丁庫的頭文件,明確給予結構的名稱:

//#define _Opaque struct {unsigned long x;} * //Replaced by typedef below! 
typedef struct __Opaque {unsigned long x;} * _Opaque; 

這顯然是不好的,因爲我們不希望儘可能地接觸到庫中。另一個更糟糕的選擇是將所有函數簽名中的類型轉換爲void*並將它們轉換回它們各自的類型。並且有最壞的選擇來重寫純C中的每個受影響的函數...

所以,我的問題是:是否有比修補庫更好的選項?我忽略了一個簡單的解決方案嗎?解決這個問題的最好方法是什麼?

+0

看不到這與C有什麼關係。你的問題是C++包裝器的名稱改變,不是? – 2012-08-13 12:11:36

+2

這可能是升級到C++ 11的一個很好的參數,它沒有這個限制。 – ecatmur 2012-08-13 12:13:10

+1

@JensGustedt,是正確的,但我也將它標記爲C,因爲庫是用C語言編寫的,從技術上講它是C/C++互操作性問題。 – l4mpi 2012-08-13 12:19:47

回答

3

可以以最小的變化給#define行做到這一點,利用7.1.3中的規則:的第一個typedef的名稱由申報8聲明是 該類類型(或枚舉類型)被用來表示類的類型(或枚舉類型)進行聯動僅供

#define MAKE_DUMMY2(line) dummy_ ## line 
#define MAKE_DUMMY(line) MAKE_DUMMY2(line) 
#define _Opaque struct {unsigned long x;} MAKE_DUMMY(__LINE__), * 

這給出了HandleRequest等最小的鏈接。

+0

這個工作,但仍然需要在圖書館的變化。另外,你能否解釋一下你的答案 - 爲什麼使用兩個MAKE_DUMMY而不是一個? – l4mpi 2012-08-13 13:54:41

+1

@ l4mpi這兩個定義對於擴展'__LINE__'是必要的。預處理器是*怪異的*;請參閱http://stackoverflow.com/questions/1597007/creating-c-macro-with-and-line-token-concatenation-with-positioning-macr – ecatmur 2012-08-13 13:56:16

+0

我們詢問了圖書館供應商是否可以包含此修復程序。如果他們拒絕,我們會在本地使用它...直到有人有時間和勇氣來測試我們是否可以安全地將我們的編譯器和C++版本從gcc4.1/C++ 98升級到gcc4.7/C++ 11。 – l4mpi 2012-08-14 13:11:08

1

您可以通過聲明新類型來引入名稱,它們只包含這些元素。使用這些類型作爲參數。

namespace MON { 
struct t_handle { 
    Handle handle; 
}; 

class MyCls { 
public: 
    static void* myFct(t_handle handle); 
    ... 
}; 
} 
+2

ctors和轉換運算符不能被聲明,因爲它們的簽名將包含一個匿名類型。 – ecatmur 2012-08-13 12:17:09

+0

@ecatmur感謝注意(+1)。我對ctors和轉換操作的建議與OP使用的編譯器/設置不兼容 - 刪除了。 – justin 2012-08-13 12:32:55

+0

這可能是正確的做法,但我很猶豫要實現這一點,因爲它需要在大部分代碼庫中進行更改。 – l4mpi 2012-08-13 13:49:58

0

這似乎工作:

class MyCls { 
    public: 
    typedef _Opaque MHandle; 
    static void* myFct(MHandle handle) { 
     return 0; 
    } 
}; 
1

如果你願意修改界面上你的方法,你可以做略勝void *

struct CHandle { 
    void *p; 
    CHandle(void *p): p(p) { } 
}; 
struct CRequest { 
    void *p; 
    CRequest(void *p): p(p) { } 
}; 

static CHandle make(Handle handle) { return CHandle(handle); } 
static Handle get(CHandle handle) { return static_cast<Handle>(handle.p); } 
static CRequest make(Request request) { return CRequest(request); } 
static Request get(CRequest request) { return static_cast<Request>(request.p); } 

這裏,CHandleCRequest有關聯,因此可以在你的方法簽名中使用; makeget的超載具有內部鏈接,因此可以與匿名類型進行交互。你可以把它放在標題中,即使是static函數。

您必須修改代碼,以便在例如MyCls::myFct調用庫中,你包裝參數get和返回值make

+0

爲什麼在這種情況下聲明'make'工作?它在其簽名中使用匿名結構......應該不會導致同名的問題? – l4mpi 2012-08-13 14:13:54

+1

@ l4mpi在這種情況下,因爲'make'是靜態的,所以沒有外部鏈接。 – ecatmur 2012-08-13 14:17:29