How to build a shared C++ library for iOS and Android

Yuzhou Zhu
Aug 7, 2018 · 7 min read
Image for post
Image for post

At SafetyCulture, we want to ship things fast, so our customers can improve workplace safety and quality fast. In order to do that, our mobile engineering team is looking for new alternatives to develop our apps without having to write same code multiple times on different platforms.

There are quite a few options available out there with pros and cons. However it’s not easy to just pick one. When we validate those options, we have following guidelines in mind:

  1. No compromise on user experience. Regardless which solution we choose, our users should still be able to use high quality app which feels natural and smooth.
  2. Little to no compromise on developer experience. We will definitely do something different than native development, however we don’t want to feel that we have to make a big commitment on a new stack for just sharing code. It should be flexible enough for us to move to any direction we want in future without throwing all code away.

After spending a few days on research and discussion, we landed our eyes on Djinni, a share code solution by Dropbox which allows you to share main business logic in C++ and still have user interface handcrafted natively to achieve best user experience. You can also expose some platform specific APIs through the bridge for C++ to use, and expose back to user interface if necessary. This is really powerful as we can write a clean, thin UI layer using the same set of APIs on both platforms.

How to Setup Djinni

Add Djinni as submodule to your git project:

git submodule add https://github.com/dropbox/djinni.git deps/djinni
git submodule update --init --recursive

Create your .djinni interface description file, for example:

question = record {
id: string;
title: string;
order: i32;
}
page = record {
id: string;
title: string;
order: i32;
questions: list<question>;
}
form = record {
id: string;
name: string;
pages: list<page>;
}
shared_core = interface +c {
static create(): shared_core;
generate_form(number_of_pages: i32, questions_per_page: i32): form;
prefix_string(input: string) : string;
}

The interface description is pretty straightforward: we have three records which consist a single nested data structure: a form contains multiple pages of multiple questions.

The shared_core is the name of the interface which we are going to implement in C++. You can also define an interface which can be implemented in Objective-C and Java, we will show you an example later in this article. Let’s just focus on this simple example for now. it’s worth noting that we defined a static create method for the shared_core which takes no parameters. This makes more sense when you need to pass in your platform implemented objects in as you will need them in most cases.

The next step is to create a shell script which take the above .djinni file as input and generate all the bridging code for us. The .sh file looks like this:

#! /usr/bin/env bash### Configuration
# Djinni IDL file location
djinni_file="demo.djinni"
# C++ namespace for generated src
namespace="demo"
# Objective-C class name prefix for generated src
objc_prefix="SC"
# Java package name for generated src
java_package="com.safetyculture.demo"
### Script
# get base directory
base_dir=$(cd "`dirname "0"`" && pwd)
# get java directory from package name
java_dir=$(echo $java_package | tr . /)
# output directories for generated src
cpp_out="$base_dir/generated-src/cpp"
objc_out="$base_dir/generated-src/objc"
jni_out="$base_dir/generated-src/jni"
java_out="$base_dir/generated-src/java/$java_dir"
# clean generated src dirs
rm -rf $cpp_out
rm -rf $jni_out
rm -rf $objc_out
rm -rf $java_out
# execute the djinni command
deps/djinni/src/run \
--java-out $java_out \
--java-package $java_package \
--ident-java-field mFooBar \
--cpp-out $cpp_out \
--cpp-namespace $namespace \
--jni-out $jni_out \
--ident-jni-class NativeFooBar \
--ident-jni-file NativeFooBar \
--objc-out $objc_out \
--objc-type-prefix $objc_prefix \
--objcpp-out $objc_out \
--idl $djinni_file

This script depends on the Djinni submodule we added in the beginning, so make sure you’ve done that. Once you run this script, you will see a new directory called generated-src. It contains four sub-directories: cpp, objc, jni, java which have all the code you need for your iOS or Android project.

To compile the Dinjini generated code for an iOS project:

  1. Create a directory called platforms/ios and create your iOS project inside of it.
  2. Add files within deps/djinni/support-lib/objc, generated-src/objc and generated-src/cpp to your iOS project. DO NOT use copy option.
  3. Rename the main.m of your iOS project to be main.mm; this turns it into a Objective-C++ file.

All done, you should be able to build your project without error.

Now let’s start writing some code. Create a directory called shared and create two files called shared_core_impl.hpp and shared_core_impl.cpp. That’s where we write our shared code in C++. And don’t forget to add these two files to your iOS project as well.

The header file shared_core_impl.hpp looks like this:

#include "shared_core.hpp"namespace demo {
class SharedCoreImpl : public demo::SharedCore {
public:
SharedCoreImpl();
Form generate_form(int32_t number_of_pages, int32_t questions_per_page);
std::string prefix_string(const std::string & input);
};
}

What this does is we create a new class called SharedCoreImpl which implements the interface SharedCore. I won’t post the full implementation code here, but the logic is pretty simple. The generate_form method takes 2 integer parameters, generates and returns a Form object with the number of pages and questions provided via parameters. The prefix_string just puts a hello in front of any input string and return the new string.

It’s really easy to use your C++ code in an iOS project; here is a short example:

#import "ViewController.h"
#import "SCSharedCore.h"
@interface ViewController ()
@property (nonatomic, strong) SCSharedCore *coreAPI;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_coreAPI = [SCSharedCore create];
}
- (void)generateForm() {
// generate a form contains 500,0000 questions
SCForm * form = [_coreAPI generateForm:500 questionsPerPage:1000];
}
@end

That’s it! Not too hard, right? The setup process is a bit different on Android, you will need to import both generated jni and java code. However the concept is similar: importing automatically generated headers, and add C++ implementation code to your project. That’s why we need to put those C++ files in a shared directory outside of iOS or Android project.

The Architecture

Image for post
Image for post

We borrowed concept from Redux and Flux where data only flows one direction. View sends actions and then render new state. We will have specific managers written in C++ for each screen or UI flow. When a view is initiated, the view controller is responsible to instantiate and setup the manager object. The view only needs to worry about when to send actions and how to render state. The entire business logic is implemented in manager and hidden from UI layer which can help us to write really clean UI code.

This also makes writing unit tests really easy; since the UI layer now does rendering and send actions separately, we can create mock manager which only feeds different states to view, and test the rendering logic. We can also create another mock manager to verify if correct actions are sent when user interacts with UI.

Platform Interaction

Here is a good example for a platform interface:

ui_platform_support = interface +o +j {
post_task_in_background_thread(task: task);
post_task_in_main_thread(task: task);
}

Here we take advantage of existing concurrency/threading APIs from each platforms. The diagram below shows how UI is interacting with platform APIs:

Image for post
Image for post

Networking

What We’ve Learnt

C++ is not that scary

Be aware of performance overhead

There are extra steps to setup

That’s it! We are still in process of adopting this new architecture within mobile team. We will keep sharing our experience along the way.

Do you have experience building cross platform mobile apps? Join our engineering team and help us build products that impact lives.

SafetyCulture Engineering

Building something that truly impacts people's lives

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store