On embedded devices there are scenario's that you do not want to execute in JavaScript, like video encoding, or other CPU-limited tasks. This article will show you how to cross compile a simple C++ application and how to call it from JanOS. For a more complex application with dependencies, see our blog post about compiling WebP.
Here is the simple 'Hello world' application that we're going to cross-compile. Save it somewhere on your hard drive and name it 'hello.cpp'.
#include <iostream>
int main()
{
std::cout << "Hello JanOS!";
return 0;
}
We can compile and run this with gcc via g++ hello.cpp -o hello && ./hello. Now the binary that was generated by g++ won't run on JanOS, as it's compiled for your current architecture. Most phones (at least the one's we support) use the ARMv7 architecture. So to run this file we need to use a cross-compiler.
The Android NDK comes with tools to create a 'toolchain', which sets up gcc to cross compile. First, install the NDK for your platform. Open a terminal and navigate to the directory where the NDK is extracted, and make a toolchain for arm via:
$ build/tools/make-standalone-toolchain.sh --arch=arm --install-dir=/tmp/ndk-arm
Now go back to the folder where you created hello.cpp, and you can now use the newly created gcc to compile it instead. Afterwards push it to your device, and verify that it worked.
$ /tmp/ndk-arm/bin/arm-linux-androideabi-g++ hello.cpp -o hello
# now remount so we can write to the /system partition
$ adb remount
# push to a directory, /system/bin is the bin directory on GeeksPhone
# might be different on other devices
$ adb push hello /system/bin/hello
# test whether it worked
$ adb shell hello
It's also possible to use the NDK build system to cross compile for you. It's similar to a normal Makefile, but targeted at Android systems. You can f.e. specify which libraries to link. If you encounter a project that already has an Android port, it's likely that it will already have an Android.mk file.
To get started, first create a new directory called jni/. Then move hello.cpp into this folder. Then (in the same folder) create two new files.
APP_STL := stlport_static
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := hello.cpp
include $(BUILD_EXECUTABLE)
Now go back to the folder above /jni, and run:
$ ndk-build
[armeabi] Compile++ thumb: hello <= hello.cpp
[armeabi] Executable : hello
[armeabi] Install : hello => libs/armeabi/hello
You can now push this executable to the device, same way as above.
Now to call the binary from JavaScript code we use the mozOs.exec API. Easiest to test is to attach the debugger, and in the console type:
navigator.mozOs.exec('hello')
.then(res => console.log(res.exitCode, res.stdout))
.catch(err => console.error('Failure', err));
// returns 0 "Hello JanOS!"