用C++编写一个C库的包装器

17

我有一个使用面向对象形式编写的旧版C库。典型的函数如下:

LIB *lib_new();
void lib_free(LIB *lib);
int lib_add_option(LIB *lib, int flags);
void lib_change_name(LIB *lib, char *name);

我想在我的C++程序中使用这个库,因此我认为需要一个C++封装器。 以上所有内容似乎都可以映射到以下内容:

class LIB
{
    public:
         LIB();
         ~LIB();
         int add_option(int flags);
         void change_name(char *name);
...
};

我以前从未编写过 C++ 封装 C 的代码,并且找不到太多关于此的建议。这种创建 C++/C 封装器的方法是否是好的、典型的或者明智的选择?

7个回答

9

不需要C++包装器 - 您可以直接从C++代码中调用C函数。在我看来,最好不要包装C代码 - 如果您想将其转换为C ++代码 - 那么很好,请进行完整的重新编写。

实际上,假设您的C函数在名为myfuncs.h的文件中声明,则在C ++代码中,您将希望像这样包含它们:

extern "C" {
   #include "myfuncs.h"
}

为了在使用C++编译器编译时使它们具有C链接性。

8
我不同意(但不会点踩)。对于简单的C库,通常不需要包装器。然而,对于更复杂的C库,一个轻量级的C包装器可能非常有价值。一个例子让我想到了:Tibco在Tibrv周围有一个非常轻量级的包装器,这非常有帮助。 - Nathan Ernst
1
我同意Nathan的观点 - 对于某些复杂度较高、特别是第三方库,将其封装为C++库通常会使它们对C++客户端更加有用。这还可以让我们在C库提供的功能基础上实现更多特性(比如缓存)。 - Bojan Resnik
WFC是Windows API的轻量级封装。MFC因所涉及的部分不同(例如消息映射),从轻量级到相当重量级都有可能。 - Ben Voigt
在我的经验中,没有太多关于封装C库的“通用”技巧。在C代码返回错误时抛出异常是可行的。确切地说,要完成这个任务需要取决于C库。但是,如果不支持异常处理,那么封装也就没有太大意义了。 - Bojan Resnik
1
将C函数封装为C++类的一个主要原因是为了实现单元测试。请参考https://github.com/mlb5000/CFunctionWrapperGenerator,了解从C头文件生成这些类的方法。 - Matt Baker
显示剩余9条评论

9

我通常只编写一个简单的RAII包装器,而不是包装每个成员函数:

class Database: boost::noncopyable {
  public:
    Database(): handle(db_construct()) {
        if (!handle) throw std::runtime_error("...");
    }
    ~Database() { db_destruct(handle); }
    operator db_t*() { return handle; }
  private:
    db_t* handle;
};

使用类型转换运算符,可以将其与C函数一起使用:
Database db;
db_access(db, ...);  // Calling a C function with db's type conversion operator

2

我认为只有在使用库的过程中,编写包装器才有意义。在你的情况下,你不必传递一个LIB*,并且可能可以在堆栈上创建LIB对象,因此我认为这是一种改进。


1
仍然需要在对象之间传递LIB::LIB对象。在堆栈上分配LIB::LIB对象只会给您自动语义,因为新的C LIB对象可能会在构造过程中分配在堆上。但尽管如此,C++包装器仍然很方便。 - el.pescado - нет войне

2

通常我会这样处理。我也不会使用char*,而是使用std::string。


1
一个 C++ 包装器并不是必需的。你可以在你的代码中调用 C 函数而没有任何阻碍。

1

如果可以的话,我也会考虑将LIB重命名为更好的名称,至少改成"Lib"。

更改名称可能是一个getter setter...

所以,GetName(char *) SetName(char *)

然后再考虑将其更改为std::string而不是char*,如果SetName(const std::string name),它将接受char*作为参数。

即,慢慢地转向C++风格。


0
假设C库的分配/释放实例是create_instance和destroy_instance,并且它公开了一个名为call_function的函数,但它不提供实例的深层复制API,那么这将起作用:
class Wrapper
{
public:
    Wrapper(): m_instance(create_instance(), destroy_instance) {}

    explicit operator bool() const
    {
        // null check
        return bool(m_instance);
    }

    void callFunction()
    {
        call_function(m_instance.get());
    }

private:
    std::unique_ptr<instance, decltype(&destroy_instance)> m_instance;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接