首页 > 解决方案 > 通过包装器从 C 访问 C++ API 时,如何访问枚举类型?

问题描述

我有一个需要连接到 C++ API 的 C 程序。我在这里询问并得到了很好的建议,从而创建了一个“包装器”。

所以,在 API 中有一个名为“APIName::ReturnCode”的类型,我想创建一个 C 等价物,所以我做了以下工作:

在 c_api.h 中:

#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

typedef void* API_ReturnCode_t;
EXTERNC API_ReturnCode_t api_returncode_init();
EXTERNC void api_returncode_destroy(API_ReturnCode_t rc);

#undef EXTERNC

在 c_api.cpp 中:

#include "c_api.h"
#include "/path/to/api/api.h"

API_ReturnCode_t api_returncode_init() {
        return new APIName::ReturnCode;
}

void api_returncode_destroy(API_ReturnCode_t untyped_ptr) {
        APIName::ReturnCode* typed_ptr = static_cast< APIName::ReturnCode*>(untyped_ptr);
        delete typed_ptr;
}

所以我将它编译成一个库并将其包含在我的主程序中,我可以使用如下内容:

API_ReturnCode rc;

定义一个变量。

但是,我的下一个问题是如何以类似的方式定义枚举类型。因此,该 api 对错误代码有以下定义:

namespace APIName {

    typedef enum ReturnCode_enum ReturnCode;

    enum ReturnCode_enum {
        RC_OK                               ,   // success
        RC_ERROR                            ,   // general error
        RC_NOT_AVAILABLE                    ,   // feature is not available
    };
}

如何在我的包装器中重新创建它,以便我可以在我的代码中执行类似的操作:

API_ReturnCode rc = API_RC_OK;

谢谢你。

标签: c++c

解决方案


因此,经过一些澄清后,我的原始答案不再适用——但仍保留在此答案之下。

由于无法以任何方式更改原始 C++ API,因此您的可用选项受到更多限制。

你希望能够做到:

API_ReturnCode rc = API_RC_OK;

Butrc是一种不透明的类型 ( void*),需要用 - 销毁api_returncode_destroy- 所以这不可能以一种简单而理智的方式实现(不会混淆谁拥有API_RC_OK调用)。最大的问题是,如果我们可以生成一个API_RC_OK实例,它会导致所有权问题。例如:

API_ReturnCode rc = API_RC_OK;
api_returncode_destroy(rc); // is this good? is 'API_RC_OK' a resource that needs deleting?

它在更复杂的表达式中变得更加混乱。

由于该APIName::ReturnCode_enum类型只是经典的 C 风格enum,可以隐式转换为 an int,因此您最好的办法是尝试通过将' 定义为来保留int-like 属性:API_ReturnCode_t

typedef int API_ReturnCode_t;

然后任何 C++ 包装的调用都可以传播这些值int

不幸的是,为了能够在另一端接收这些值,您需要在此处重复一些工作,以某种方式手动重新创建这些常量。我想到了几种方法,各有利弊。

这里不方便的事实是,因为您试图在 C 中公开 C++ 中定义的值,所以您需要以某种方式在另一端重新编码。您不能简单地包含 C++ 标头并在 C 中使用它,因为它们是不同的语言,而 C++ 包含 C 不理解的功能。

1.使用extern常量

一种可能的方法是使用extern const在源中从基础值中定义的值,因此您不会被困在复制这些值本身。例如:

c_api.h

EXTERNC extern const API_ReturnCode_t API_RC_OK;
EXTERNC extern const API_ReturnCode_t API_RC_ERROR;
EXTERNC extern const API_ReturnCode_t API_RC_NOT_AVAILABLE;

c_api.cpp

extern "C" {

const API_ReturnCode_t API_RC_OK = APIName::RC_OK;
const API_ReturnCode_t API_RC_ERROR = APIName::RC_ERROR;
const API_ReturnCode_t API_RC_NOT_AVAILABLE = APIName::RC_NOT_AVAILABLE;

} // extern "C"

这种方法的好处是您不必手动设置API_RC_OKto0API_RC_ERRORto1等——因此这些值不是强耦合的。

需要注意的是,这些extern常量在初始化期间不能(安全地)从其他对象中static使用,因为不能保证何时设置这些值。如果你没有做太多的static初始化,这不应该有任何问题。

2. 重复努力

如果枚举不大,并且不太可能变得更大,那么显而易见的简单方法就是这样做:

#define API_RC_OK 0
#define API_RC_ERROR 1
#define API_RC_NOT_AVAILABLE 2

或其等价物。extern优点是,与常量相比,它可以在任何地方使用。这里明显的缺点是包装器与被包装的库强耦合。如果这是一个大型枚举,或者一个可能经常/定期更改的枚举——这可能不是最好的方法。

3. 定义一个可能正交的枚举

另一种选择是定义正交枚举。这需要重新定义您关心的枚举案例,并通过单独的函数调用来翻译它们。这会导致更多的努力——所以取决于你在做什么,这可能不是最好的情况。

c_api.h

typedef enum {
    API_RC_OK,
    API_RC_ERROR,
    API_RC_NOT_AVAILABLE,
    /* other states? */
} API_ReturnCode_t; 

**c_api.cpp

API_ReturnCode_t to_return_code(APIName::ReturnCode rc)
{
    switch (rc) {
        case APIName::RC_OK: return API_RC_OK;
        case APIName::RC_ERROR: return API_RC_ERROR;
        case APIName::RC_NOT_AVAILABLE: return API_RC_NOT_AVAILABLE;
    }
    return API_RC_NOT_AVAILABLE;
}

在您的包装器代码中,您收到的任何地方APIName::ReturnCode现在都将转换为 an API_ReturnCode_t,然后再返回给 C 调用者。

这种方法的好处是枚举器不再需要同步,并且您可以限制要抽象出的枚举案例(假设您不想要 1-1 映射)。

这也为将来升级到不同版本的 C++ 库提供了一种更简单的方法,因为所有内容都由翻译功能内部化。如果 C++ 库引入了新状态,您可以选择将其中一些值合并在一起,以使其更容易被 C 客户端使用。

这种方法的明显缺点是它需要更多的工作,因为您要定义一个单独的层次结构和一个在开始时会非常相似的翻译系统。为了以后更高的回报,前期工作更多。


旧答案

您的课程没有任何特定于 C++ 的内容ReturnCode_enum。它实际上是用更传统的 C++ 风格编写的(例如,不enum class用于范围界定),这使得它可以直接在 C 中使用。

那么为什么不在头文件中定义enumc_api.h并在你的 C++ 中使用它呢?这可能需要根据其中存储的内容更改您的不透明句柄定义;但是这样你就会有一个枚举的定义。

typedef您可以使用别名或别名将 C 符号带入 C++ 命名空间using,这允许对值进行更 C++ 式的发现。

在 c_api.h 中:

enum Api_ReturnCode_enum {
    RC_OK                               ,   /* success */
    RC_ERROR                            ,   /* general error */
    RC_NOT_AVAILABLE                    ,   /* feature is not available */
};
/* 
or 'typedef enum { ... } Api_ReturnCode_enum;' if you want don't want to specify
'enum' every time in C
*/

在您的 C++ API 中:

#include "c_api.h"

namespace APIName { // bring it into this namespace:

    // Alias the "Api_" prefixed enum to be more C++ like
    typedef Api_ReturnCode_enum ReturnCode;

    // alternative, in C++11 or above:
    // using ReturnCode = Api_ReturnCode_enum;
}

推荐阅读