Calling C++ in Swift

I’ve been developing an iOS app with image processing capability. The app is programmed in Swift 4, yet the image processing library is written in C++ for performance and compatibility concerns. Therefore there is a need to bridge C++ class to Swift.

Since Swift cannot call C++ directly yet, we need an Objective-C wrapper (more specifically, an Objective-C++ wrapper) as the middleman. Therefore we will first talk about how to call Objective-C function from Swift, and then we’ll talk about how to call C++ from Objective-C++.

Calling Objective-C from Swift

Forget about C++, let’s assume you have been able to connect C++ with Objective-C perfectly, or your library is purely written in Objective-C, so now your only concern is calling Objective-C classes and functions from Swift.

I assume you already have an app written in Swift running, if not, you can follow this post to get a minimalistic app running.

To create an Objective-C class, select File -> New -> File…. In the dialog that appears, choose Objective-C File and click Next.

New Objective-C File

Let’s name the new file “ClassOc”, and click Next. In the dialog that follows, choose a directory to store the file. You may simply use the default, and click Creat. Then you’ll be prompted to create an Objective-C bridging header.

Create Bridging Header

Click Create Bridging Header, and on the left side of your Xcode IDE, you can see two files created, ClassOc.m and SingleViewPrj-Bridging-Header.h. The first part of the bridging header’s name is the project’s name.

The bridging header informs the Swift side what Objective-C classes, functions, and variables are available. Let’s finish up implementing our Objective-C class first before we get back to the bridging header.

Similar to the procedure of creating the Objective-C class, create a header file for the class by selecting File -> New -> File…, and choosing Header File. We name the header file “ClassOc.h”

We define a simple class in Objective-C as follows:

ClassOc.h

#ifndef ClassOc_h
#define ClassOc_h

#import <Foundation/Foundation.h>

@interface ClassOc : NSObject

@property NSString *myString;

- (void) printString;

- (void) updateString;

@end

#endif /* ClassOc_h */

ClassOc.m

#import <Foundation/Foundation.h>
#import "ClassOc.h"

// implementation of the objective-c class
@implementation ClassOc

- (instancetype)init {
    self = [super init];
    if (self) {
        self.myString = @"ClassOc is working!";
    }
    return self;
}

- (void) printString {
    NSLog(@"%@", self.myString);
}

- (void) updateString {

}

@end

This class simply prints its string property as a log. To let the Swift know the existance of this class, we add line #import “ClassOc.h” to SingleViewPrj-Bridging-Header.h.

Implement Bridging Header

We update the viewDidLoad() function in VidwController.swift as follows to call the Objective-C function.

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    NSLog("View Loaded");
    
    let classOc = ClassOc();
    classOc.updateString();
    classOc.printString();
}

Build and run, you should see “ClassOC is Working” printed in the log as shown in the previous figure. Now you have successfully implemented an Objective-C class and bridged it to Swift.

Calling C++ from Objective-C

In this section, we’ll focus on calling C++ from Objective-C. Here Swift is irrevalent, this section is applicable if you are writing an app purely in Objective-C and C++.

We first add ClassCpp.cpp and its header ClassCpp.hpp to the project following a similar procedure as we added ClassOc.m. We write our C++ class as follows.

ClassCpp.cpp

#include "ClassCpp.hpp"
#include <string>

using namespace std;

ClassCpp::ClassCpp() {
    myStringCpp = "ClassCpp's string is passed";
}

string ClassCpp::getString() {
    return myStringCpp;
}

ClassCpp.hpp

#ifndef ClassCpp_hpp
#define ClassCpp_hpp

#include <stdio.h>
#include <string>

using namespace std;

class ClassCpp {
public:
    ClassCpp();
    string getString();
private:
    string myStringCpp;
};

#endif /* ClassCpp_hpp */

This class has a getString() method which returns a C++ string. To call this class in Objective-C, we update ClassOc.m as follows, with the changes highlighted in yellow.

#import <Foundation/Foundation.h>
#import "ClassOc.h"
#import "ClassCpp.hpp"

ClassCpp* classCpp;

// implementation of the objective-c class
@implementation ClassOc

- (instancetype)init {
    self = [super init];
    if (self) {
        self.myString = @"ClassOc is working!";
        classCpp = new ClassCpp();
    }
    return self;
}

- (void) printString {
    NSLog(@"%@", self.myString);
}

- (void) updateString {
    std::string cppString = classCpp->getString();
    self.myString = [NSString stringWithUTF8String:cppString.c_str()];
}

@end

If you build and run from here, a compiler error would occur, because ClassOc.m is only compiled as C and Objective-C file. The compiler cannot understand C++ syntax, neigher can it find C++ files like <string>. To fix this, we rename the file as ClassOc.mm. Here, *.mm file is called an Objective-C++ file, it signals the compiler to compile it as both Objective-C and C++.

Build and run, you will see “ClassCpp’s string is passed” printed in the log. This string is passed from C++ to Objective-C. This shows we have successfully linked C++ to Objective-C, and to Swift.

Final notes

In case you did not add the bridging-header automatically, or you want to add the bridging-header manually, add the .h file, and modify the settings as shown in the following figure.

Bridging Header

Sometimes when compiling C++ with Objective-C, you receive an error like the following.

fatal error: ...: can't open input file: <string> (No such file or directory)

Yet you know the C++ header file is present, and maybe you can even open it in the IDE. If this error occurs, usually it is because you are calling C++ from a file other than .mm. For example, if I put #import “ClassCpp.hpp” in ClassOc.h, I’ll get that error. This is because the compiler takes ClassOc.h as Objective-C code, and as it tries to find <string> in the directory for C headers, it fails to find it.

The solution, of course, is to only have C++ code in .mm file, and keep the rest of Objective-C files pure from C++.