Question

I'm trying to wrap an existing 3rd party C++ library to a C interface, so that it can be used in bindings for another language. I'm having trouble figuring out how to wrap a namespaced enum, as opposed to just redefining it:

// Existing C++ 3rd party library header
namespace foo {
    enum Fruit {
        APPLE = 0,
        ORANGE
    }
}

So then I have my wrapped.{h,cpp} with an extern "C" block, and I just can't figure out how to export the foo::Fruit enum into the C interface

// wrapped.h
#ifdef __cplusplus
extern "C" {
#endif

// I don't want to do this
typedef enum Fruit {
    APPLE = 0,
    ORANGE
} Fruit;

#ifdef __cplusplus
}
#endif
#endif

Is it possible to export (mirror) foo::Fruit from the C++ library into my C wrapper as Fruit?

Était-ce utile?

La solution

edit: I just noticed that you wanted to wrap an existing library without modifying it.

I fear you are about out of luck then. In general there is just no way of extracting just the enum members out of C++ code without the C compiler choking.

In practice you've got the choice whether to programmatically translate your own set of enumerations into the C++ versions in the interface, try to mirror the C++ exactly and place a bunch of static assertions to double-check, or in theory even filtering them out through scripts.

There are simply no good options here I'm afraid. For the record I would tend to prefer the first of these bad options.


Personally I probably would be lazy and just stick to the C version.

Still, if required and the number of constants is large you can do a bit of macro magic to get a single definition with C-style "namespaces" as required.

First a single header defining all enum entries through a macro:

/* Fruit.h */
FOO_ENUM(APPLE) = 0,
FOO_ENUM(ORANGE)

Then in the C header:

/* C interface */
typedef enum {
#   define FOO_ENUM(id) FOO_##id
#   include "Fruit.h"
#   undef FOO_ENUM
} Foo_Fruit_t;

And finally in the C++ header:

// C++ interface
namespace Foo {
    enum Fruit_t {
#       define FOO_ENUM(id) id
#       include "Fruit.h"
#       undef FOO_ENUM
    };
}

There are many alternatives of course. For instance if you don't mind polluting the global namespace in C++ then can always define the full enumeration directly in the C interface and copy the individual enum members in the C++ version of the definition.

Autres conseils

I ran into this particular problem with enums in a C wrapper for a C++ library recently and it caused quite a headache.

My solution is shown in the following mostly minimal working example but it is terribly inelegant in places. It is essentially a translation approach.

One must be wary not to declare anything twice with regard to the enums. The example passes int, a string or array of char and an enum.

A library header written in C++. This is the library that will be wrapped. MyClass.h:

#ifndef __MYCLASS_H
#define __MYCLASS_H

#include <iostream>

namespace MyNamespace {

using namespace std;

enum EnumControlInterface {HIDController=1, UVCController=2};

class MyClass {

private:
         int m_i;

         string m_text;

         EnumControlInterface _controller;

public:
         MyClass(int val);

         ~MyClass();

         void int_set(int i);

         void string_set(string text);

         int int_get();

         string string_get();

         void writeEnum(EnumControlInterface MyInterface);

         EnumControlInterface readEnum();
    };
};
#endif

The C++ implementation of MyClass.cpp:

#include "MyClass.h"

namespace MyNamespace {

    MyClass::MyClass(int val) {
        cout << "MyClass is being created" << endl;
        cout << "The parameter passed to the MyClass constructor is: " << val << endl;
    }

    MyClass::~MyClass() {
        cout << "MyClass is being destroyed" << endl;

    }

    void MyClass::writeEnum(EnumControlInterface MyInterface) {
        _controller = MyInterface;
        cout << "The interface control Enum is set in MyClass.cpp as: " << _controller << endl;
    }

    EnumControlInterface MyClass::readEnum() {
        return _controller;
    }

    void MyClass::string_set(std::string text) {
        m_text = text;
    }

    string MyClass::string_get() {
        return m_text;
    }

    void MyClass::int_set(int i) {
        m_i = i;
    }

    int MyClass::int_get() {
        return m_i;
    }

}

A "C wrapper" header file MyWrapper.h which wraps MyClass.h:

#ifndef __MYWRAPPER_H
#define __MYWRAPPER_H

#ifdef __cplusplus
namespace MyNamespace {
    extern "C" {
#endif

        typedef enum WrapperEnumControlInterface {WrapHIDController=1, WrapUVCController=2} WrapperEnumControlInterface;

        typedef struct MyClass MyClass;

        MyClass* newMyClass(int val);

        void MyClass_int_set(MyClass* v, int i);

        int MyClass_int_get(MyClass* v);

        void MyClass_string_set(MyClass* v, char* text);

        char* MyClass_string_get(MyClass* v);

        void MyClass_writeEnum(MyClass* v, WrapperEnumControlInterface MyInterface);

        WrapperEnumControlInterface MyClass_readEnum(MyClass* v);

        void deleteMyClass(MyClass* v);

#ifdef __cplusplus
    }
}
#endif
#endif

The "C wrapper" implementation is written in a mixture of C and C++. Specifically the function definitions have to be C and the parameters passed and returned have to be C types as well. Inside the functions and inside the preprocessor areas __cplusplus C or C++ should be fine.

One can not, for example, ask a function inside the extern "C" block to accept the type std::string. It would defeat the objective of the wrapper: to expose only C code that operates the underlying C++ library. extern "C" determines what is exposed without name mangling (see questions about name mangling in C++). __cplusplus is defined by (many) C++ compilers.

MyWrapper.cc:

#include "MyClass.h"
#include "MyWrapper.h"
#include <vector>

namespace MyNamespace {
extern "C" {

    MyClass* newMyClass(int val) {
            return new MyClass(val);
    }

    void deleteMyClass(MyClass* v) {
            delete v;
    }

    void MyClass_int_set(MyClass* v, int i) {
        v->int_set(i);
    }

    int MyClass_int_get(MyClass* v) {
        return v->int_get();
    }

    void MyClass_string_set(MyClass* v, char* text) {
        //convert incomming C char* to a C++ string
        string stringToSend = string(text);

        cout << "the string received from the program by the wrapper is " << text << endl;
        cout << "the string sent to the library by the wrapper is " << stringToSend << endl;

        v->string_set(stringToSend);
    }

    char* MyClass_string_get(MyClass* v) {

        string result = v->string_get();

        cout << "the string received from the library by the wrapper is " << result << endl;

        // Convert the C++ string result to a C char pointer and return it. Use vectors to do the memory management.
        // A vector type of as many chars as necessary to hold the result string
        static vector<char> resultVector(result.begin(), result.end());

        cout << "the data in the vector who's pointer is returned to the program by the wrapper is: " << &resultVector[0] << endl;

        return (&resultVector[0]);
    }

    void MyClass_writeEnum(MyClass* v, WrapperEnumControlInterface MyInterface) {
        v->writeEnum((EnumControlInterface)MyInterface);
    }

    WrapperEnumControlInterface MyClass_readEnum(MyClass* v) {
        EnumControlInterface result = v->readEnum();
        return (WrapperEnumControlInterface)result;
    }

}
}

A C program that calls the C++ library via the wrapper Cproject.c:

#include "MyWrapper.h"
#include "stdio.h"

int main(int argc, char* argv[]) {

    struct MyClass* clsptr = newMyClass(5);

    MyClass_int_set(clsptr, 3);

    printf("The int read back in Cproject.c is: %i\n", MyClass_int_get(clsptr));

    MyClass_writeEnum(clsptr, WrapUVCController);

    printf("The enum read back in Cproject.c is: %d\n", MyClass_readEnum(clsptr));

    MyClass_string_set(clsptr, "Hello");

    char *textReadBack = MyClass_string_get(clsptr);

    printf("The text read back in Cproject.c is: %s \n", textReadBack);

    deleteMyClass(clsptr);

    return 0;
}

And just for completeness a C++ project that calls the C++ library directly without the use of the wrapper CPPProgram.cpp, so short!:

#include "MyClass.h"
#include <iostream>

using namespace std;
using namespace MyNamespace;

int main(int argc, char* argv[]) {

    MyClass *c = new MyClass(42);

    c->int_set(3);

    cout << c->int_get() << endl;

    c->writeEnum(HIDController);

    cout << c->readEnum() << endl;

    c->string_set("Hello");

    cout << c->string_get() << endl;

    delete c;
}

The MyClass C++ class is compiled to a static library, the wrapper is compiled to a shared library there is no particular reason, both could be static or shared.

The C program that calls the wrapper library (Cproject.c) must be linked with a C++ compiler (G++ etc.)

Obviously this example doesn't have a serious application. It is based on https://www.teddy.ch/c++_library_in_c/ in terms of structure but with the enum bits added in.

Often the person writing the wrapper doesn't have access to the source code of the library they're trying to wrap (MyClass.cpp in this case) they will have the .so or .dll or .a or .lib for Linux and Windows shared and static libraries respectively. It is not necessary to have the source code for the C++ library. Only the header file(s) for the C++ library are needed to write an effective wrapper.

I have written this out partly to provide a more verbose answer to the original question, one that can be copied compiled easily and played around with but also because this is the only way I have been able to solve the problem so far and it is not satisfactory in my view. I would like to be able to wrap the enums in the same way one wraps the public member functions not re-create the enums inside the wrapper with slightly different names.

Sources of related information that proved useful:

https://www.teddy.ch/c++_library_in_c/

How to cast / assign one enum value to another enum

Developing C wrapper API for Object-Oriented C++ code

Converting a C-style string to a C++ std::string

Returning pointer from a function

std::string to char*

Of course all unsafe, wrong etc. coding practices are my fault entirely.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top