简体   繁体   中英

When accessing C++ API from C via a wrapper, how do I access enum types?

I have a C program which I need to connect to a C++ API. I asked on here and was given great advice, leading to creating a "wrapper".

So, in the API there is a type called "APIName::ReturnCode", and I wanted to create a C equivalent, so I've done the following:

In c_api.h:

#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

typedef void* API_ReturnCode_t;
EXTERNC API_ReturnCode_t api_returncode_init();
EXTERNC void api_returncode_destroy(API_ReturnCode_t rc);

#undef EXTERNC

in c_api.cpp:

#include "c_api.h"
#include "/path/to/api/api.h"

API_ReturnCode_t api_returncode_init() {
        return new APIName::ReturnCode;
}

void api_returncode_destroy(API_ReturnCode_t untyped_ptr) {
        APIName::ReturnCode* typed_ptr = static_cast< APIName::ReturnCode*>(untyped_ptr);
        delete typed_ptr;
}

So I compile that into a library and include it in my main program, and I can use things like:

API_ReturnCode rc;

to define a variable.

However, my next issue is how to define enumerated types in a similar way. So, the api has the following definition for error codes:

namespace APIName {

    typedef enum ReturnCode_enum ReturnCode;

    enum ReturnCode_enum {
        RC_OK                               ,   // success
        RC_ERROR                            ,   // general error
        RC_NOT_AVAILABLE                    ,   // feature is not available
    };
}

How do I recreate this in my wrapper so that I can do something like this in my code:

API_ReturnCode rc = API_RC_OK;

Thank you.

So after some clarification, my original answer is no longer applicable -- but is still retained below this answer.

Since the original C++ API cannot be altered in any way, you are much more limited in your available options.

You want to be able to do:

API_ReturnCode rc = API_RC_OK;

But rc is an opaque type ( void* ) that requires being destroyed with api_returncode_destroy -- so this won't be possible in an easy and sane way (not without confusing who owns the API_RC_OK calls). The biggest issue is that if we could produce an API_RC_OK instance, it leads to questionable ownership. For example:

API_ReturnCode rc = API_RC_OK;
api_returncode_destroy(rc); // is this good? is 'API_RC_OK' a resource that needs deleting?

And it gets more confusing in more complicated expressions.

Since the APIName::ReturnCode_enum type is just a classic C-style enum , which is implicitly convertible to an int , your best-bet here would be to try to preserve the int -like property by making API_ReturnCode_t 's definition be:

typedef int API_ReturnCode_t;

Then any of the C++-wrapped calls can propagate the values as this int

Unfortunately to be able to receive these values on the other side, you will need to duplicate some effort here by manually re-creating these constants in some way. There are a few approaches that come to mind, all with pros and cons.

The inconvenient truth here is that, because you're trying to expose values defined in C++ in C, you'll need to somehow re-encode this on the other side in some way. You can't simply include the C++ header and use it in C, since they are different languages and C++ contains features that C doesn't understand.

1. Use extern constants

One possible approach is to use extern const values that get defined in the source from the underlying values, so you aren't stuck duplicating the values themselves. For example:

c_api.h

EXTERNC extern const API_ReturnCode_t API_RC_OK;
EXTERNC extern const API_ReturnCode_t API_RC_ERROR;
EXTERNC extern const API_ReturnCode_t API_RC_NOT_AVAILABLE;

c_api.cpp

extern "C" {

const API_ReturnCode_t API_RC_OK = APIName::RC_OK;
const API_ReturnCode_t API_RC_ERROR = APIName::RC_ERROR;
const API_ReturnCode_t API_RC_NOT_AVAILABLE = APIName::RC_NOT_AVAILABLE;

} // extern "C"

The good thing with this approach is that you aren't stuck manually setting API_RC_OK to 0 , and API_RC_ERROR to 1 , etc -- so these values are not strongly coupled.

The thing to watch out for is that these extern constants would not be (safely) usable from other objects during static initialization, since it's not guaranteed when these values will be set. If you aren't doing much static initialization, this shouldn't be of any concern.

2. Just duplicate the effort

If the enum is not large, and not likely to grow much larger, the obvious simple approach is to just do:

#define API_RC_OK 0
#define API_RC_ERROR 1
#define API_RC_NOT_AVAILABLE 2

or some equivalent thereof. The pro is that this can be used anywhere, compared to extern constants. The obvious con here is that the wrapper is strongly coupled to the wrapped library. If this is a large enumeration, or an enum that is likely to change often / regularly -- this is approach is probably not the best.

3. Define a possibly-orthogonal enumeration

One other option is to define an orthogonal enumeration instead. This requires re-defining the enum cases that you care about, and translating them through a separate function call. This results in more effort -- so depending on what you're doing, this may not be the best case.

c_api.h

typedef enum {
    API_RC_OK,
    API_RC_ERROR,
    API_RC_NOT_AVAILABLE,
    /* other states? */
} API_ReturnCode_t; 

**c_api.cpp

API_ReturnCode_t to_return_code(APIName::ReturnCode rc)
{
    switch (rc) {
        case APIName::RC_OK: return API_RC_OK;
        case APIName::RC_ERROR: return API_RC_ERROR;
        case APIName::RC_NOT_AVAILABLE: return API_RC_NOT_AVAILABLE;
    }
    return API_RC_NOT_AVAILABLE;
}

In your wrapper code, anywhere you receive an APIName::ReturnCode you would now translate to an API_ReturnCode_t before returning back to the C caller.

The nice thing about this approach is that the enumerators no longer need to be in-sync, and that you can restrict the enum cases that you want to abstract out (assuming you don't want 1-1 mapping).

This also presents an easier way to upgrade in the future to different versions of the C++ library, since everything is internalized by the translation function. If the C++ library introduces new states, you can choose to coalesce some of those values together in a way that may make it more consumable by the C client.

The obvious downside with this approach is that it takes more work, since you're defining a separate hierarchy and a translation system that will be quite similar in the beginning. It's more work up-front for a higher return later on.


Old Answer

There is nothing specific to C++ about your ReturnCode_enum class. It's actually written in a more legacy-C++ style (eg not using enum class for scoping), which makes it usable in C directly.

So why not define the enum in the c_api.h header file instead, and use it in your C++ as well? This may require changing your opaque handle definition depending on what is stored in it; but this way you would have exactly 1 definition of the enumeration.

You can bring the C symbol into C++ namespaces using either typedef or using aliases, which allow a more C++-esque discovery of the values.

In c_api.h:

enum Api_ReturnCode_enum {
    RC_OK                               ,   /* success */
    RC_ERROR                            ,   /* general error */
    RC_NOT_AVAILABLE                    ,   /* feature is not available */
};
/* 
or 'typedef enum { ... } Api_ReturnCode_enum;' if you want don't want to specify
'enum' every time in C
*/

In your C++ API:

#include "c_api.h"

namespace APIName { // bring it into this namespace:

    // Alias the "Api_" prefixed enum to be more C++ like
    typedef Api_ReturnCode_enum ReturnCode;

    // alternative, in C++11 or above:
    // using ReturnCode = Api_ReturnCode_enum;
}

I wouldn't hide error code enums in opaque handles.

Create a new enum and convertion functions in the c_api.cpp file

c_api.h

typedef enum {
    RC_OK,
    RC_ERROR,
    RC_NOT_AVAILABLE
} ReturnCode_copy;

ReturnCode_copy some_function(...);

c_api.cpp

static ReturnCode_copy convert(APIName::ReturnCode code) {
    switch(code) {
        //return correct ReturnCode_copy
    }
}

ReturnCode_copy some_function(...) {
    auto code = //some api function returning error code
    return convert(code);
}

or you could be naughty and just copy the values directly in your new enum and just static_cast directly without the convert function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM