2012-08-13 156 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