将C ++命名空间枚举包装到C接口

时间:2013-12-26 22:32:05

标签: c++ c enums extern

我正在尝试将现有的第三方 C ++库包装到C接口,以便它可以用于另一种语言的绑定。我无法弄清楚如何包装命名空间枚举,而不是重新定义它:

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

那么我的包裹。{h,cpp}包含extern "C"块,我无法弄清楚如何将foo::Fruit枚举导出到C接口

// 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

是否可以将{镜像)foo::Fruit从C ++库导出到我的C包装器Fruit

2 个答案:

答案 0 :(得分:3)

编辑:我刚注意到你想要包装现有的库而不修改它。

我担心你的运气不好。通常,如果没有C编译器阻塞,就没有办法从C ++代码中只提取枚举成员。

在实践中,您可以选择是否以编程方式将您自己的枚举集转换为接口中的C ++版本,尝试完全镜像C ++并放置一堆静态断言进行双重检查,或者理论上甚至通过脚本过滤掉它们。

我担心这里没有好的选择。为了记录,我倾向于选择这些不良选项中的第一个。

<小时/> 我个人可能会很懒,只是坚持C版。

但是,如果需要并且常量的数量很大,你可以根据需要做一些宏魔术来获得具有C风格“命名空间”的单一定义。

首先通过宏定义所有枚举条目的单个标头:

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

然后在C标题中:

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

最后在C ++标题中:

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

当然有很多选择。例如,如果您不介意在C ++中污染全局命名空间,则可以始终直接在C接口中定义完整枚举,并在定义的C ++版本中复制各个枚举成员。

答案 1 :(得分:1)

我最近在C ++库的C包装器中遇到了enums的这个特殊问题,这引起了很大的麻烦。

我的解决方案在以下几乎是最小的工作示例中显示,但它在某些地方非常不优雅。它本质上是一种翻译方法。

必须警惕不要对enums声明任何两次。该示例传递intstringchar数组以及enum

用C ++编写的库头。这是将被包装的库。 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

MyClass.cpp的C ++实现:

#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&#34; C包装&#34;包装MyClass.h的头文件MyWrapper.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

&#34; C包装&#34;实现是用C和C ++的混合编写的。具体来说,函数定义必须是C,并且传递和返回的参数也必须是C类型。在函数内部和预处理器区域内__cplusplus C或C ++应该没问题。

例如,人们不能要求extern "C"块内的函数接受类型std::string。它会破坏包装器的目标:仅公开操作底层C ++库的C代码。 extern "C"确定在没有名称修改的情况下公开的内容(请参阅questions about name mangling in C++)。 __cplusplus由(许多)C ++编译器定义。

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

}
}

通过包装器Cproject.c调用C ++库的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;
}

只是为了完整性,一个C ++项目直接调用C ++库而不使用包装器CPPProgram.cpp,所以很简单!:

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

MyClass C ++类被编译为静态库,包装器被编译为共享库,没有特别的原因,两者都可以是静态的或共享的。

调用包装器库(Cproject.c)的C程序必须与C ++编译器(G ++等)链接。

显然,这个例子并没有严肃的应用。它在结构方面基于https://www.teddy.ch/c++_library_in_c/,但添加了enum位。

编写包装器的人通常无法访问他们试图包装的库的源代码(在这种情况下为MyClass.cpp),他们将拥有.so或.dll或。 a或.lib分别用于Linux和Windows共享和静态库。没有必要拥有C ++库的源代码。只需要C ++库的头文件来编写有效的包装器。

我写这篇文章的部分原因是为原始问题提供了一个更详细的答案,一个可以轻松复制编译并且可以使用的问题,但也因为这是我迄今为止能够解决问题的唯一方法。在我看来,这并不令人满意。我希望能够以包装公共成员函数的方式包装enums,而不是在包装器中重新创建enums,名称略有不同。

证明有用的相关信息来源:

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*

当然,所有不安全,错误等编码习惯都是我的错。