This article is part of a talk presented at the App Builders conference in Lugano that took place on the 30th of April 2019. You can find the other part talking about code sharing between iOS and Android using J2ObjC here.
Why code sharing?
At Ubique, we like to write native apps, in our oppinion the best UX is achieved with fluid and native UI. If the presentation layer needs to be native, on the contrary the business logic can be shared. When business logic is shared then it is written once, therefore reducing maintenance, and allowing us to enforce a similar behaviour between iOS and on Android.
In most of our projects there is at least some code that we share between the platforms, from database logic, autocompletion and signature validation to complex graph drawing, custom maps and many more. For a lot of those cases, we use C++ as a shared language. In this article, we share our setup and experience of the advantages and disadvantages using C++ to develop mobile apps.
C++ on Mobile
C++ is a first class citizen on iOS. If you still write Objective-C code these days, you can embed C++ by simply renaming a
.m source file to
.mm (this also enables a few neat swiftifying syntax tricks) and start writing code. Swift on the other hand has no C++ interoperability. You can bridge C and C++ functions, but you’ll need a Objective-C++ wrapper to expose classes.
Using the Native Development Kit (NDK), Android has always relied on C++ for performance-critical code or to build on existing libraries. The Java Native Interface (JNI) defines how to interact between such native code and bytecode written in Java or Kotlin. After setting up the toolchain, this essentially boils down to defining and using a native method in Java:
… and declaring the function on the C-side implementation:
In our early days of sharing C++ code, writing those cryptical bridging declarations was a substantial and cumbersome part of the development process. Fortunately, in 2014 Dropbox released Djinni, a tool to generate both Objective-C++ and JNI bindings.
Djinni uses a simple Interface Definition Language (IDL) to describe the interface that should be exposed from C++. The IDL uses records for pure-data value objects and interfaces for objects which are either implemented in C++ or Objective-C/Java.
From enums, booleans and numbers to strings, arrays and maps – Djinni supports types to define interfaces for all kind of shared code bases and allows to implement custom bridged data types if needed. Read the full introduction for an complete overview.
Unfortunately, Dropbox recently announced that will no longer maintain Djinni. But the project is mature and stable enough that we’ll keep building on it and don’t expect any problems in the foreseeable future.
Our Djinni setup
For every project, we have an additional GIT repository that contains the interface definitions and the shared C++ code. Unfortunately, the Djinni command only accepts a single IDL file. We therefore use Make to call
djinni/run for every IDL in the repo. Right now, we manually trigger Make for Android. On iOS, we added it as a Run Script Phase to the build process.
On both platforms, we add our C++ implementation as well as the generated files directly to the project. We used to build a static library, but adding everything to the same project makes development and debugging a lot easier. On Android, we setup the CMakeList to include all files in a few directory, on iOS we manually have to add new files.
Feel free to contact me for sample project that demonstrates this setup.
Adding a new C++ code
Let’s look at the steps that you need to add a new feature to the shared codebase. In this example, we’ll start the the Xcode/iOS side, but you could begin with Android just as well. We want to compute the distance between two strings, the so-called Levenshtein distance.
We first need to add a new IDL file for the interface definition. Our Makefile expects
.jinni as a file extension. The implementation is really straightforward: We define a new interface and add the
+c to tell djinni, that we will implement the interface in C++. Then we add a static method that takes two Strings and returns an integer.
After adding a new IDL file, we build our project once to generate the new bridging files. Then we manually add them to the Xcode project. To expose the files to Swift, we also have to add them to the bridging header. As mentioned, you’ll manually have to call Make to generate the files for the Android JNI bridging.
Finally, we can start with the implementation of our small “business logic”. We create a new C++ file and add it to our Xcode project. I simply copied the function declaration from the generated header and added the implementation from Rosettacode.
Use in projects
That’s it, we’ve implemented the distance in C++ and can use it in Objective-C, Swift, Java or Kotlin.
So is C++ a suitable language for your own mobile app project? Yes, iff you are willing to write C++ code. If and only if, that is. A well-structured djinni setup will take away most of the bridging hassle, but it won’t reduce the complexity of writing C++ code. Its not too hard to break something in your app if you write Objective-C, Swift, Java or Kotlin, but it is so much easier with C++. Nevertheless, if you or your team already have some experience with and see a certain beauty in the language, you will be able to share code using a incredibly fast, mature and well-supported language.
- Both platforms build on C++ themselves, thus the language is officially and well supported
- C++ is fast and mature
- Djinni takes away most of the bridging hassle, especially JNI
- C++ is a difficult to learn
- C++ is easy to break
- You’ll need a third language to define the interfaces
- Autogenerated virtual methods are an unnecessary level of abstraction
- Debugger-support is not quite as good as with the platforms primary languages
- Again, it’s C++
In the last years we built a lot of complex apps using C++ and djinni. The setup allowed us to get the most out of the available hardware, iterate fast without worrying about divergence and focus on what should be most important for all developers: Create a great user experience.