通过包装器从 C 访问C++ API 时,如何访问枚举类型

When accessing C++ API from C via a wrapper, how do I access enum types?

本文关键字:访问 何访问 类型 枚举 API 包装 C++      更新时间:2023-10-16

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

因此,在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++ API 无法以任何方式更改,因此可用选项受到更多限制。

您希望能够执行以下操作:

API_ReturnCode rc = API_RC_OK;

但是rc是一种不透明的类型(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,可以隐式转换为int,你最好的选择是尝试int通过使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_OK设置为0API_RC_ERROR设置为1等 - 因此这些值不是强耦合的。

需要注意的是,在初始化static期间,这些extern常量不会(安全地)从其他对象使用,因为不能保证何时设置这些值。如果您没有做太多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,您现在都会在返回到 C 调用方之前转换为API_ReturnCode_t

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

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

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


旧答案

关于您的ReturnCode_enum课程,没有什么具体C++。它实际上是以更C++传统风格编写的(例如不使用enum class进行范围界定),这使得它可以直接在 C 中使用。

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

可以使用typedefusing别名将 C 符号引入C++命名空间,这样就可以更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;
}

我不会在不透明的句柄中隐藏错误代码枚举。

在c_api.cpp文件中创建新的枚举和转换函数

c_api.h

typedef enum {
RC_OK,
RC_ERROR,
RC_NOT_AVAILABLE
} ReturnCode_copy;
ReturnCode_copy some_function(...);

c_api.cpp

static ReturnCode_copy convert(APIName::ReturnCode code) {
switch(code) {
//return correct ReturnCode_copy
}
}
ReturnCode_copy some_function(...) {
auto code = //some api function returning error code
return convert(code);
}

或者你可能很顽皮,只是直接在新的枚举中复制值,而直接static_cast而不使用转换函数。