如何为在 C 中使用 C++ 代码创建包装器?

Posted

技术标签:

【中文标题】如何为在 C 中使用 C++ 代码创建包装器?【英文标题】:How to create wrapper for using C++ code in C? 【发布时间】:2019-06-05 23:43:11 【问题描述】:

我正在尝试为 C++ 第三方库做一个 C 包装器,因为我需要在我的 C 项目中使用它。 我看过 C++ 类包装器的示例,但我不了解该过程,也无法包装 C++ 结构。

我要包装的结构:

struct confGlobal
    long int device;
    string name;
    int number;
    string uid;
    string message;
    bool mode;

    confiGlobal():deviceLONG_MAX, numberINT_MAX
;

struct product
    string id;
    string name;
;
struct category
    unsigned char id;
    string name;
    string description;

    category():idUCHAR_MAX
;
struct subCategory
    unsigned char id;
    string name;
    string description;
    unsigned char idRoot;

    subCategory():idUCHAR_MAX, idRootUCHAR_MAX
;
struct confPartner
    vector<struct product> tableProduct;
    vector<struct category> tableCategory;
    vector<struct subCategory> tableSubCategory;
;

调用此方法:

class my_API 
public:
    static my_API* Instance(struct confGlobal cGlobal,
                        struct confPartner cPartner);
   ... another methods ...
private:
    virtual ~my_API();
    struct confGlobal cGlobal;
    struct confPertner cPartner;
;

我需要填充这个结构并从 C 调用 my_API::Instance(),但我的尝试没有成功。

包装器.h

#ifdef __cplusplus
extern "C" 
#endif
struct confGlobal_wpr; // An opaque type that we'll use as a handle
typedef struct confGlobal_wpr confGlobal_wpr;
confGlobal_wpr *confGlobal_create(unsigned int device,
                     char *name,
                     int number,
                     char *uid,
                     char *message,
                     unsigned char mode);

struct product_wpr
    char id[4];
    char name[30];
;
typedef struct product_wpr product_wpr;

struct category_wpr
    unsigned char id;
    char name[3];
    char description[30];
;
typedef struct category_wpr category_wpr;

struct subcategory_wpr
    unsigned char id;
    char name[3];
    char description[30];
    unsigned char idRoot;
;
typedef struct subCategory_wpr subCategory_wpr;
struct confPartner_wpr; // An opaque type that we'll use as a handle
typedef struct confPartner_wpr confPartner_wpr;
confPartner_wpr *confPartner_create(Product_wpr tableProducts[],
                     unsigned char numProducts,
                     Category_wpr tableCategories[],
                     unsigned char numCategories,
                     SubCategory_wpr tableSubCategories[],
                     unsigned char numSubCategories);

struct my_api_wpr;
typedef struct my_api_wpr my_api_wpr;

my_api_wpr *my_api_create(struct confGlobal_wpr cGlobal,
                     struct confPartner_wpr cPartner);

#ifdef __cplusplus

#endif

包装器.cpp

confGlobal_wpr *confGlobal_create(unsigned int device,
                     char *name,
                     int number,
                     char *uid,
                     char *message,
                     unsigned char mode)

    confGlobal_wpr *cg;
    struct confGlobal confiGlobal;
    confiGlobal.name = name;
    confiGlobal.device = device;
    confiGlobal.number = number;
    confiGlobal.uid = uid;
    confiGlobal.message = message;
    if (mode == 0)
        confiGlobal.mode = false;
    else
        confiGlobal.mode = true;
    return cg;

void confGlobal_destroy(confGlobal_wpr *cg)

    if (cg == NULL)
        return;
    delete static_cast<confGlobal_wpr *>(cg->instance); // ERROR: invalid static_cast from type ‘confGlobal’ to type ‘confGlobal_wpr*’
    free(cg);


confPartner_wpr *confPartner_create(product_wpr tableProducts_wpr[],
                     unsigned char numProducts,
                     category_wpr tableCategories_wpr[],
                     unsigned char numCategories,
                     subCategory_wpr tableSubCategories_wpr[], 
                     unsigned char numSubCategories)

    unsigned char i=0;

    confPartner_wpr *cc;
    struct confPartner cPartner;
    vector< struct product> tableProduct;
    vector< struct category> tableCategory;
    vector< struct subCategory> tableSubCategory;

    for (i=0; i<numProducts; i++)
    
        struct product p;
        p.id = tableProducts_wpr[i].id;
        p.name = tableProducts_wpr[i].name;
        tableProduct.push_back(p);
    
    cPartner.tableProduct = tableProducts;

    for (i=0; i<numCategories; i++)
           
        struct category c;
        c.id = tableCategories_wpr[i].id;
        c.nombre = tableCategories_wpr[i].name;
        c.descripcion = tableCategories_wpr[i].description;
        tableCategory.push_back(c);
    
    cPartner.tableCategory = tableCategory;

    for (i=0; i<numSubCategories; i++)
    
        struct subZona sc;
        sc.id = tableSubCategories_wpr[i].id;
        sc.name = tableSubCategories_wpr[i].name;
        sc.description = tableSubCategories_wpr[i].description;
        sc.idRoot = tableSubCategories_wpr[i].idRoot;
        tableSubCategory.push_back(sc);     
    
    cPartner.tableSubCategory = tableSubCategory;
    return cc;



my_api_wpr *my_api_create(struct confGlobal_wpr confiGlobal_wpr,
                      struct confPartner_wpr confiPartner_wpr)

    my_api_wpr *my_api;
    my_API *obj;
    my_api = (typeof(my_api))malloc(sizeof(*my_api));
    obj    = my_API::Instance(confiGlobal_wpr, confiConsorcio_wpr);
    /* With this compile and linked OK
    confGlobal cg;
    confPartner cc;
    obj    = my_API::Instance(cg, cc);
    */
    my_api->obj = obj;

    return my_api;

void my_api_destroy(ct_api_wpr *my_api)

    if (my_api == NULL)
        return;
    delete static_cast<my_API *>(my_api->ptr_api); // ERROR: ‘virtual my_API::~my_API()’ is private within this context
    free(my_api);

编译链接时的输出错误:

g++ -shared -o libwrapper.so *.cpp wrapper.h -l:libthird-party.a -L. -ldl -lrt -Wl,-rpath /usr/local/lib -lc
In function ‘my_api_wpr* my_api_create(confGlobal_wpr, confPartner_wpr)’:
error: no matching function for call to ‘my_API::Instance(confGlobal_wpr&, confPartner_wpr&)’
  obj    = my_API::Instance(confiGlobal_wpr, confiConsorcio_wpr);
                                                               ^
my_API.h:30:20: note: candidate: static my_API* my_API::Instance(confGlobal, confPartner)
     static my_API* Instance(struct confGlobal cGlobal, struct confiPartner cPartner);
                    ^~~~~~~~
my_API.h:30:20: note:   no known conversion for argument 1 from ‘confGlobal_wpr’ to ‘confGlobal’

【问题讨论】:

而不是char 看似任意长度,在许多情况下30 个字符肯定太小了,使用char*。您可能会发现,如果没有 C++,就无法将 C char[]char* 桥接到 C++ std::string 为什么要通过confGlobal_wpr - 方法需要confGlobal 要使用 c++ 代码,您需要使用 C 调用约定(extern C)定义包装函数,并在您的 C 项目中调用它们。在这些导出的函数中,您可以使用 C++ 代码。 ***.com/questions/31903005/…的可能重复 【参考方案1】:

您忘记了CT_API::Instance 不理解您在 C 中创建的用于包装 C++ 结构的“句柄”类型。如果您阅读它,这正是错误消息告诉您的内容。您必须将它们转换回适当的 C++ 类型。

首先,由于您使用“创建”样式的例程来构建结构并将它们作为指针返回,因此您应该考虑让您的my_api_create 函数接受指针。特别是因为生成的句柄类型是前向声明的结构,在 C 中没有可见的定义,并且您的 C 客户端无法取消引用它们。

这突出了另一个问题。您也没有在 C++ 中正确使用这些句柄。

所以,一次只做一件事......

您在 C 中的创建例程应声明为:

my_api_wpr *my_api_create(struct confGlobal_wpr* cGlobal, struct confPartner_wpr* cPartner);

在 C++ 方面,您需要真正定义您的句柄类型。比如:

extern "C" struct confGlobal_wpr 
    struct confGlobal instance;
;

extern "C" struct confPartner_wpr 
    struct confPartner instance;
;

extern "C" struct my_api_wpr 
    my_API *ptr;
;

现在,你的创作:

confGlobal_wpr *confGlobal_create(unsigned int device,
                                  char *name,
                                  int number,
                                  char *uid,
                                  char *message,
                                  unsigned char mode)

    confGlobal_wpr *cg = new confGlobal_wpr;
    struct confGlobal& cGlobal = cg->instance; //<-- note the reference
    // TODO: populate cGlobal as usual
    return cg;


confPartner_wpr *confPartner_create(product_wpr tableProducts_wpr[],
                                    unsigned char numProducts,
                                    category_wpr tableCategories_wpr[],
                                    unsigned char numCategories,
                                    subCategory_wpr tableSubCategories_wpr[],
                                    unsigned char numSubCategories)

    confPartner_wpr *cc = new confPartner_wpr;
    struct confPartner& cPartner = cc->instance; //<-- note the reference
    // TODO: populate cPartner as usual
    return cc;


my_api_wpr *my_api_create(struct confGlobal_wpr* cGlobal, struct confPartner_wpr* cPartner)

    my_api_wpr *my_api = new my_api_wpr;
    my_api->ptr = CT_API::Instance(cGlobal->instance, cPartner->instance);
    return my_api;

您还应该为以上所有内容添加相应的_destroy 方法。

【讨论】:

非常感谢。我已经在我的代码中进行了修改,目前效果很好。现在,我了解了一些包装事实,我需要包装许多其他结构和方法。还有一个问题:如何销毁指针包装结构(confGlobal)和指针包装类(ct_api_wpr)?我在最初的询问中添加了销毁方法。 我没有意识到你可以在结构上使用extern "C"。你一定要吗?它甚至有什么作用吗?我怀疑它没有。 @crossmax 好吧,API 本身很奇怪,因为它实际上是一个单例。所以从技术上讲,my_api_create 可以简单地是my_api_initialize 并且不返回任何指针。对于confGlobal_wpr,您的销毁函数将只是void confGlobal_destroy(confGlobal_wpr* confGlobal) delete confGlobal; confPartner_wpr 类似。 @Omnifarious 实际上我并不是 100% 确定,因为结构体已经在标题中前向声明为 extern "C"。我所做的基本上与将定义包装在extern "C" 中相同。但它可能不是必需的,因此这可能是错误的。当然在extern的传统用法中,据我所知,它只需要在声明中。大约 20 年以来,我不需要用 C 语言编写 C++ 库包装器;)如果您找到了答案,请在此处留言。【参考方案2】:

包含除 C 和 C++ 通用的原始数据类型以外的任何内容的结构在很大程度上不能以您想要的方式包装。在这种特殊情况下,您的结构中有::std::strings。而且绝对不能从 C 中合理地访问它们。

此外,结构包含bool,我不知道新版本的 C 标准是否有bool 或以这样的方式定义它,它会导致布局兼容的结构与同一平台上的 C++ 实现.

这个问题有解决方案。但是它们涉及在 C 中使用指向结构的不透明指针,并且总是调用函数来访问其方法。我将尝试举一个例子来说明这对于一个非常简单的结构是如何工作的。

更仔细地查看您的代码,您似乎需要一种 thunk 层,它采用 C 结构并(在 C++ 中)将其手动转换为 C++ 结构并返回指向您动态分配的 C++ 结构的指针然后可以传递给已经暴露给 C 的其他 C++ 函数。

【讨论】:

【参考方案3】:

要在 C 项目中使用 C++ 代码,您需要使用 C 调用约定定义包装函数 - extern "C"(关闭 C++ 名称修饰/装饰),并仅在 C 项目中调用它们。在这些 C 函数中,您可以使用 C++ 代码。仅将 C 理解的类型传递给 C 包装函数。您可以创建中间结构来将数据传递给 C 包装函数。然后,您需要将数据复制到 C++ 类期望的类型。在您的特殊情况下,您错误地传递了 confGlobal_wpr 包装器结构,但 C++ 方法需要 confGlobal,并且编译器直接对此抱怨。

以下是可观察到的 sn-p 如何从 C 代码中使用 C++ 代码: Foo.h

#include <string>

class Bar

public:
    Bar(std::string s) : s_(s)
    
    
    std::string s_;
;

class Foo

public:
    Foo(Bar) 
;

CWrappers.h // 将此标头包含到 C 项目中

struct BarWrapper

    char data[100] = "";
;

#ifdef __cplusplus
extern "C" 
#endif

BarWrapper createFoo(char *c);

#ifdef __cplusplus

#endif

包装器.cpp

#include <algorithm>

#include "Foo.h"
#include "CWrappers.h"

// input and output to this C function should be understandable for C
BarWrapper createFoo(char *c)

    // inside you can use C++
    std::string data(c);
    Bar bar(data);
    Foo * foo = new Foo(bar);
    BarWrapper barWrapper;
    std::copy(data.begin(), data.end(), barWrapper.data);
    return barWrapper; // pack data to C struct wrapper (that has no C++ specific)

C 项目

        #include "CWrappers.h"

        int main()
        
            BarWrapper barWrapper = createFoo((char *)"test");
        

【讨论】:

以上是关于如何为在 C 中使用 C++ 代码创建包装器?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 python C++ 嵌入构建 boost 示例

如何为现有的 Javascript 库创建 Angular 库包装器?

如何为 ui-bootstrap 日期选择器创建 angularJs 包装器指令?

通过在 C++ 接口周围创建 C 包装器,在 FORTRAN 中调用 C++ dll 文件 [关闭]

如何为 boost::serialization 指定一个 nvp 包装器?

为 C++ 库创建一个 c 包装器