1

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?

jdi
  • 90,542
  • 19
  • 167
  • 203
  • 1
    There are certainly ways of doing that, the quickest and dirtiest being a verbatim #include in both places, but presumably you'd also like to prefix the C version with a manual "namespace" of some sort? I suppose you'll probably end up either manually copying the enum value assignments in the C++ version or using macros to mangle the identifiers – doynax Dec 26 '13 at 22:40
  • @doynax Yea so far I have had to just manually copy the enum definition directly into my `extern "C"` because I can't reference the original namespaced enum in the 3rd party library. Is there a macro solution? – jdi Dec 26 '13 at 23:01
  • I think it's not possible. I would use some dumb text processing tool (`sed`/`awk`/...) to automate the translation, then put it into the makefile, and job done... – Karoly Horvath Dec 26 '13 at 23:03

2 Answers2

3

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.

doynax
  • 4,285
  • 3
  • 23
  • 19
  • `Fruit.inc` would be a better name: it's not a header file, but it's meant to be `#include`'d. Otherwise, a perfectly normal (that's C/C++ interop, after all) solution. – Joker_vD Dec 26 '13 at 23:23
  • I'm not all that adverse to just copying over the enum definitions, since ultimately it looks about the same amount of work as using the macros, since you still have to write them all out. I was hoping for some magical `typedef` solution that aliased `foo::Fruit` to `Fruit`. Oh well. In case it matters at all to your answer, specifically I am wrapping [this lib](https://github.com/imageworks/OpenColorIO/blob/master/export/OpenColorIO/OpenColorTypes.h), so that I can produce bindings for the `Go` language. – jdi Dec 27 '13 at 00:24
  • Accepting this answer, basically taking away that its not super straight-forward and easier to just copy across :-) Thanks! – jdi Dec 27 '13 at 03:32
  • @jdi: I suspect that your main problem won't be copying the enums but rather maintaining them such that a library revision doesn't (silently) break your separately maintained wrapper. If you intend to precisely match the build-in types then STATIC_ASSERT is your friend, and better check sizeof(enum T) as well since it may change depending on the values in the set – doynax Dec 27 '13 at 07:55
  • Oh cool. Ya the maintainability was my motivation for posting the question. I already knew I could just redefine them. But adding these checks sounds like a great idea to catch changes. Although it would seem to be a breaking API change for them to shuffle around enum values. So my guess is that I can rely on major version consistency. If they go to API v2 then I will want to track that. – jdi Dec 27 '13 at 19:53
1

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.

Community
  • 1
  • 1
  • So the key point in this answer is that you do copy the enum definition into your C wrapper and just cast between the wrapped and original values. That is what I was saying I didn't want to do. The reason being that I wanted to avoid having to keep the wrapped enum in sync with any changes to the source enum. – jdi Aug 17 '16 at 19:25
  • Indeed as I said in the answer: "this is the only way I have been able to solve the problem so far and it is not satisfactory in my view." I'm hoping that if I make it super easy for someone who gets paid to code all day long to compile the example they will take pity on me and provide the same example but done another way in which the `enums` are wrapped my some means that I do not know. – James E Green Aug 17 '16 at 19:55
  • Fair enough. But it seems from the other answer that it is not possible to "export" the enum type from one namespace to the root namespace :( – jdi Aug 17 '16 at 20:12
  • It looks that way to me too, very frustrating. I've also realized that the line in MyWrapper.cc that assigns the vector should be `static vector resultVector(result.begin(), result.end()+1);` otherwise the read that occurs in Cproject.c will try to read the 5 bytes of Hello followed by one terminator byte `\0` which (according to Valgrind) is not accounted for. – James E Green Aug 18 '16 at 16:49