Sam Wood
Jun 3, 2024

Bridging to C++ Code from Swift (iOS) and Kotlin (Android)

Swift and Kotlin are great languages for app development; Apple and Google aren’t known for getting the big stuff wrong! For high-performance, cross platform code, however, C++ is King. At ROONIQ, the performance of our CV code is our highest priority, but we want to get our input data from low cost mobile devices, so bridging between languages is an important part of our software design. How do Swift and Kotlin talk to C++?

I was very excited when asked to learn iOS (actually iPhone OS) development for a large project at one of my previous companies. This was back in the days of Xcode 3 and iPhone OS 3 - a rather different development experience from today - when smartphones were just starting their unstoppable rise to success.

OK, first things first, what’s the programming language? Objective-C. So, that’s like C with classes? Well, sort of…No, not really.

NSWhat?

What are all these NS* APIs? What is NS? Ah, NeXTSTEP - that’s a rather interesting story in its own right: https://en.wikipedia.org/wiki/NeXTSTEP

Objective-C

After a few days of getting used to [[all the] brackets] and Cocoa Touch’s NSAutoreleasePools, I was ready to start interfacing my shiny new test app code with our high performance core engine, written in C++. It was surprisingly easy! Change the file type to Objective-C++, instantiate your C++ classes and start calling member functions on those objects. Brilliant!

Some of my colleagues were doing a similar exercise on Android, where they had to bridge from Java to C++ via the Java Native Interface, which meant having to go via a C interface. I felt rather sorry for them, and began to enjoy Objective-C(++) more and more.

All Good Things….

Apple released Swift for iOS in 2014. After a few years of not needing to learn it, it became clear that that position was not going to be sustainable. And Google announced first-class support for Kotlin on Android. Yes, Swift was going to replace Objective-C for iOS development.

A day or two of “This is weird” soon turned into “This language has some nice features, and it feels a lot more modern than “modern” C++. I quite like it.”

Then came the time to interface with a C++ library. Oh, we need a C interface. I was reminded of my Android friends and their fun with JNI!

A Solution

Given that I am now responsible for iOS and Android codebases, I wanted our solutions for bridging to C++ to be as similar as possible, such that it would be familiar to both iOS and Android developers, even when looking at code for the other platform.

Calling C functions from Swift is really easy: you just need to tell Xcode where to find the Objective-C Bridging Header and #import your C headers in that file. Your Swift code now has access to the C functions in those headers. Declare your interface functions with C linkage in your C++ file, then simply instantiate your C++ classes and away you go.

Kotlin to C++ is essentially the same as Java to C++: you make use of the JNI, using suitably named functions with C linkage.

I like our Swift and Kotlin C interfaces to be in the same .cpp file, so we have one point of maintenance; if you change one, you’ll probably have to change the other. And if you keep your incoming data as simple as you can (think primitives and strings), then there will probably be very little difference between them.

There’s a simple example for both languages at the end of this post.

Tip: Calling C functions via JNI from inner Classes in Kotlin or Java

Need to call a C function from within an inner class?

Don’t forget the '$' naming magic for your C function:

Java_com_rooniq_app_X_0024Y_f

And in Future?

With version 5.9, Swift now has C++ interoperability, but having a (somewhat) similar C interface between Swift/Kotlin and the underlying C++ library has a certain appeal. Let’s see how it goes….

Example

Example-App-Bridging-Header.h (iOS only)

#import "AdderBridge.hpp"

AdderBridge.hpp (iOS only)

#ifndef AdderBridge_hpp

#define AdderBridge_hpp

#ifdef __cplusplus

extern "C" {

#endif

int add(int a, int b);

#ifdef __cplusplus

}

#endif

#endif /* AdderBridge_hpp */

AdderBridge.cpp

#include "Adder.hpp"

#ifdef __APPLE__

#include "AdderBridge.hpp"

int add(int a, int b) {

    Adder adder;

    return adder.add(a, b);

}

#else // Android

#include <jni.h>

extern "C" {

JNIEXPORT jint JNICALL

Java_com_rooniq_app_Activity_add(

        JNIEnv* /*env*/,

        jobject /*activity*/,

        jint a,

        jint b) {

    Adder adder;

    return adder.add(a, b);

}

}

#endif

Adder.hpp

#ifndef Adder_hpp

#define Adder_hpp

class Adder final {

public:

   Adder() = default;

    int add(int someValue, int toAnother) const;

};

#endif /* Adder_hpp */

Adder.cpp

#include "Adder.hpp"

int Adder::add(int someValue, int toAnother) const {

   return someValue + toAnother;

}

CallerViewController.swift (iOS)

override func viewDidLoad() {

    super.viewDidLoad()

    let three = add(1, 2)

    print("three is \(three)")

}

Activity.kt (Android)

private external fun add(a: Int, b: Int) : Int

override fun onResume() {

    super.onResume()

    val three = add(1, 2)

    println("three is $three")

}