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.
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.
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.
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.
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++.