4

I'm trying to do a C wrapper for a C++ third-party library because I need to use it in my C project. I've seen examples of a C++ class wrapper but I don't undertood the process and I can't wrapper a C++ struct.

struct I want to wrapper:

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

    confiGlobal():device{LONG_MAX}, number{INT_MAX}{}
};

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

    category():id{UCHAR_MAX}{}
};
struct subCategory{
    unsigned char id;
    string name;
    string description;
    unsigned char idRoot;

    subCategory():id{UCHAR_MAX}, idRoot{UCHAR_MAX}{}
};
struct confPartner{
    vector<struct product> tableProduct;
    vector<struct category> tableCategory;
    vector<struct subCategory> tableSubCategory;
};

For call to this method:

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;
};

I need to fill this structs and call my_API::Instance() from C but my attempts have been unsuccessful.

wrapper.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

wrapper.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);
}

The output error when compile and linked with:

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’
4
  • 1
    Instead of char with seemingly arbitrary lengths, where 30 characters is bound to be way too small in many cases, use char*. You may find that bridging C char[] or char* to C++ std::string isn't possible without C++. Commented Jun 6, 2019 at 0:00
  • Why do you pass confGlobal_wpr - method requires confGlobal Commented Jun 6, 2019 at 0:09
  • To use c++ code you need define wrapper functions with C calling convention,( extern C) and call them in your C project. Inside those exported functions you can use C++ code. Commented Jun 6, 2019 at 0:12
  • Possible duplicate of stackoverflow.com/questions/31903005/… Commented Jun 6, 2019 at 0:46

3 Answers 3

2

You're forgetting that CT_API::Instance doesn't understand the "handle" types that you have created in C to wrap the C++ structures. This is precisely what the error message is telling you, if you read it. You must translate those back to the appropriate C++ types.

Firstly, since you are using "create"-style routines to build the structures and return them as a pointer, you should consider making your my_api_create function accept pointers instead. Particularly because the resulting handle types are forward-declared structs with no definition visible in C and it will not be possible for your C client to dereference them.

That highlights another issue. You are also not using these handles correctly from C++.

So, one thing at a time...

Your creation routine in C should be declared as:

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

On the C++ side, you need to actually define your handle types. Something like:

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;
};

Now, your creation:

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;
}

You should also add corresponding _destroy methods for all the above.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks so much. I've made your modifications in my code and, for the moment, works great. Now, I understand a little bit the wrapper facts and I need to wrap many another struct and methods. One more question: how I destroy the pointer wrapped struct (confGlobal) and pointer wrapper class (ct_api_wpr)? I'm adding destroy methods on my original ask.
I didn't realize you could use extern "C" on structs. Do you have to? Does it even do anything? I suspect it doesn't.
@crossmax well, it's weird for the API itself because it's actually a singleton. So technically, my_api_create could simply be my_api_initialize and return no pointer. For the confGlobal_wpr your destroy function would simply be void confGlobal_destroy(confGlobal_wpr* confGlobal) { delete confGlobal; } and similar for confPartner_wpr.
@Omnifarious I'm not 100% sure actually, since the structs are already forward-declared as extern "C" in the header. What I did was basically the same as wrapping the definitions in extern "C". But it might not be required, and so this might be wrong. Certainly in the traditional use of extern, it's only required on the declaration to my knowledge. I haven't needed to write a C++ library wrapper in C for about 20 years ;) If you find the answer, please drop a note back in here.
2

To use C++ code in C project you need define wrapper functions with C calling convention - extern "C"(turning off C++ name mangling/decoration) , and call them and only them in your C project. Inside those C functions you can use C++ code. Pass to C wrapper functions only types that C understands. You can create intermediate structures for passing data to C wrapper functions. Then you need copy data to types that C++ class expects. In you particular case you incorrectly pass confGlobal_wpr wrapper struct but C++ method requires confGlobal, and compiler complains on this directly.

Below is observable snippet how to use C++ code from C code: Foo.h

#include <string>

class Bar
{
public:
    Bar(std::string s) : s_(s)
    {
    }
    std::string s_;
};

class Foo
{
public:
    Foo(Bar) {}
};

CWrappers.h // include this header to C project

struct BarWrapper
{
    char data[100] = "";
};

#ifdef __cplusplus
extern "C" {
#endif

BarWrapper createFoo(char *c);

#ifdef __cplusplus
}
#endif

Wrapper.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 Project

        #include "CWrappers.h"

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

Comments

1

Structs that contain anything other than primitive data types common to C and C++ largely can't be wrapped in the way you want. In this particular case, you have ::std::strings in your struct. And they definitely can't be accessed reasonably from C at all.

Additionally, the structs contain bool and I don't know if newer versions of the C standard have bool or define it in such a way that it will result in layout compatible structs with C++ implementations on the same platform.

There are solutions to this problem. But they involve using opaque pointers to the struct in C, and always calling functions to access its methods. I will try to whip up an example of how this might work for a really simple struct.

Looking more carefully at your code, it looks like you need a sort of thunk layer that takes the C struct and (in C++) hand-converts it to the C++ struct and returns a pointer to a dynamically allocated C++ struct that you can then pass to other C++ functions that have been exposed to C.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.