5

I am trying to access enum's values written in C++ header from Swift. Specifically, I have this enum in OpenCV's hpp header file that I would like to expose its values to Swift. I have attempted to set up a bridging header between Swift and Objective-C, and put a wrapper around C++ enum values I would like to expose, but the compiler isn't happy about it:

imgproc.hpp: The C++ header file

enum ThresholdTypes {
    THRESH_BINARY     = 0,
    THRESH_BINARY_INV = 1,
    THRESH_TRUNC      = 2, 
    ...
};

bridging header:

#import "OpenCVWrapper.h"

OpenCVWrapper.h: My Objective-C Wrapper class to be exposed to Swift

#ifdef __cplusplus
#import <opencv2/core.hpp>
#import <opencv2/imgproc.hpp>
#endif

#import <Foundation/Foundation.h>

@interface OpenCVWrapper : NSObject

typedef enum {
    Binary = cv::THRESH_BINARY,  // ERROR: use of undeclared identifier `cv`
    BinaryInv = cv::THRESH_BINARY_INV  // ERROR: use of undeclared identifier `cv`
} ThresholdingType;

...    

@end

If I move this enum declaration and C++ code (OpenCV) import into OpenCVWrapper.mm then the compiler is ok with it, and I can also use it just fine, but I want to expose this enum to Swift so it has to be in the header file. However, something is not right when I expose the C++ enum directly in Objective-C header.

Is it possible to access C++ constants / enums directly from Objective-C header, in such a way that it can be bridge to Swift?

I have looked at using extern like this and this but the C++ constants are still not recognized in my setup.

HuaTham
  • 7,486
  • 5
  • 31
  • 50
  • 1
    As pointed out in https://stackoverflow.com/questions/35229149/interacting-with-c-classes-from-swift, the bridging header file *cannot* process C++. It does not matter if you include the .hpp file directly or indirectly via another .h file. – Martin R Aug 02 '17 at 05:09
  • Would it be easier to brigdge from C to Swift? In OpenCV 2, there was a [C API for OpenCV](http://docs.opencv.org/2.4/modules/core/doc/old_basic_structures.html). Unfortunately, maintainance seems to have been dropped in OpenCV 3: [openCV will drop C API support soon](http://answers.opencv.org/question/17546/opencv-will-drop-c-api-support-soon/). This is Q&A from 2013. Googling for "opencv c api", I found some C API doc. pages for individual classes of OpenCV 3. – Scheff's Cat Aug 02 '17 at 05:53
  • @Scheff, I'd like to avoid the solution that may stop working in the future, but thank you for the references. – HuaTham Aug 02 '17 at 13:08

2 Answers2

3

The enum values defined in OpenCV C++ library are intended to be used with APIs defined in the same library, and those APIs will need to be wrapped for use in Swift. The wrapper layer can also include code for translating between enums in C++ and Swift in such a way that changing values of C++ enums won't break Swift code. This is possible because the wrapper knows about both Swift and C++ enum values.

Let's say the C++ header file, call it CPP.h, has:

namespace cv {
    enum ThresholdTypes { 
        THRESH_BINARY     = 0,
        THRESH_BINARY_INV = 111,
        THRESH_TRUNC      = 222
    };

    void useThreshold(ThresholdTypes t);
    ThresholdTypes returnThreshold();
};

The implementation is not important for our purposes. The wrapper API, in CPPWrapper.h, exposed to Swift can look like

typedef enum {
    THRESH_BINARY,
    THRESH_BINARY_INV,
    THRESH_TRUNC,
    THRESH_UNKNOWN
} ThresholdTypesWrapper;

@interface CPPWrapper : NSObject

// The wrapper API operates in terms of wrapper `enum` values only.
// Translation between these and C++ `enum`s happens in the wrapper
// implementation.
+(void)useThreshold: (ThresholdTypesWrapper)thresholdType;
+(ThresholdTypesWrapper)returnThreshold;

@end

And here is the wrapper implementation, CPPWrapper.mm:

cv::ThresholdTypes thresholdWrapped2Native(ThresholdTypesWrapper t) {
    if (t==THRESH_BINARY) return cv::THRESH_BINARY;
    else if (t==THRESH_BINARY_INV) return cv::THRESH_BINARY_INV;
    else if (t==THRESH_TRUNC) return cv::THRESH_TRUNC;
    // This should be very unlikely.
    else throw std::runtime_error("Unknown threshold value detected.");
}

ThresholdTypesWrapper thresholdNative2Wrapped(cv::ThresholdTypes t) {
    if (t==cv::THRESH_BINARY) return THRESH_BINARY;
    else if (t==cv::THRESH_BINARY_INV) return THRESH_BINARY_INV;
    else if (t==cv::THRESH_TRUNC) return THRESH_TRUNC;
    // We could throw instead, but returning unknown is more forgiving if
    // a new C++ enum value is added.
    else return THRESH_UNKNOWN;
}

@implementation CPPWrapper

+(void)useThreshold: (ThresholdTypesWrapper)thresholdType {
    cv::useThreshold(thresholdWrapped2Native(thresholdType));
}

+(ThresholdTypesWrapper)returnThreshold {
    return thresholdNative2Wrapped(cv::returnThreshold());
}

@end

The above code snippets are not complete source code files, but should give you an idea of what's going on. The code could be made more robust in a number of ways, but that's beyond the scope of a short answer.

Anatoli P
  • 4,791
  • 1
  • 18
  • 22
  • 1
    This is basically what I am doing after a few days of asking the question. Thanks anyway. I have rep'd your answer, but will still keep the question open for possibly more streamlined approach in the future. – HuaTham Aug 23 '17 at 11:39
  • @HuaTham: Did you get the solution for your problem? I am too struggling with the same. The answer mentioned above did not work. Would you provide your solution, which is working fine? I am working with Swift4.2 – Jay Feb 22 '19 at 05:43
0

The only thing you can do is to create a brand new, independent enum in your .h file that has the same numerical values as the C++ enum, and then in your Objective-C++ file use compile time assertions (static_assert) to check that the values are the same.

typedef enum {
    Binary = 7,  // cv::THRESH_BINARY: use of undeclared identifier `cv`
    BinaryInv = 12  // cv::THRESH_BINARY_INV: use of undeclared identifier `cv`
} ThresholdingType;

Obviously putting in the right numbers, whatever they are. And checking in the .mm file, in case the original C++ header changes.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • This is the reason why I'm seeking the solution to do this: to avoid hard-coding the values. If C++ code changes the value definitions later, that Swift code will break because of this. – HuaTham Aug 02 '17 at 13:09