在C ++中包装不同的C API实现

时间:2013-03-28 14:31:17

标签: c++ c wrapper

假设我们使用gcc / g ++和随机委员会指定的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库完全链接到包装器并删除/隐藏它们的符号。

建议得到高度赞赏。

更新:最佳解决方案应同时支持两种实施方式。

注意:代码并不意味着功能正常。将其视为伪代码。

5 个答案:

答案 0 :(得分: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。

答案 1 :(得分: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中的所有函数执行此操作,那么解决所有这些问题是毫无意义的。

所以,你可以选择对你来说更合理或更可行的东西,毫无疑问,需要做一些手工工作来解决这个问题。

答案 2 :(得分:0)

如果您只想一次访问一个库实现,那么自然的方法是作为动态库

Windows中的

也适用于一次访问两个或多个库实现,因为Windows动态库提供了内部任何内容的完全封装

答案 3 :(得分:0)

IIUC ifdef是您所需要的

#define _NFOO 在nfoo lib和xfoo lib中的#define XFOO

还要记住,如果nfoo lib和xfoo lib都有一个名为Foo的函数,那么在编译期间会出错。为了避免这种情况,GCC / G ++通过名称修改使用函数重载。

然后,您可以使用ifdefs

检查xfoo是否已链接
#ifdef XFOO
//call xfoo's foo()
#endif

答案 4 :(得分:0)

链接器无法区分同一符号名称的两个不同定义,因此如果您尝试使用具有相同名称的两个函数,则必须以某种方式将它们分开。

将它们分开的方法是将它们放在动态库中。您可以从动态库中选择要导出的内容,这样您就可以导出包装器,同时隐藏底层API函数。您还可以在运行时加载动态库并一次绑定一个符号,因此即使同一个名称定义在多个符号中,它们也不会相互干扰。