Now that you know C++ (JSI Cheatsheet Part I: C++) you are ready to tackle some of the other delicacies of coding in C++, namely how compilation and linking works.
Same as before, there are many resources how basic C++ compilation work, but here are the basics
The compilation of a C++ program involves three steps:
#defines and other preprocessor directives. The output of this step is a “pure” C++ file without pre-processor directives.
The preprocessor handles the preprocessor directives, like
#define. It is agnostic of the syntax of C++, which is why it must be used with care.
It works on one C++ source file at a time by replacing
#include directives with the content of the respective files (which is usually just declarations), doing replacement of macros (
#define), and selecting different portions of text depending of
The preprocessor works on a stream of preprocessing tokens. Macro substitution is defined as replacing tokens with other tokens (the operator
## enables merging two tokens when it makes sense).
After all this, the preprocessor produces a single output that is a stream of tokens resulting from the transformations described above. It also adds some special markers that tell the compiler where each line came from so that it can use those to produce sensible error messages.
Some errors can be produced at this stage with clever use of the
The compilation step is performed on each output of the preprocessor.
The compiler parses the pure C++ source code (now without any preprocessor directives) and converts it into assembly code. Then invokes underlying back-end(assembler in toolchain) that assembles that code into machine code producing actual binary file in some format(ELF, COFF, a.out, …). This object file contains the compiled code (in binary form) of the symbols defined in the input. Symbols in object files are referred to by name.
Object files can refer to symbols that are not defined. This is the case when you use a declaration, and don’t provide a definition for it. The compiler doesn’t mind this, and will happily produce the object file as long as the source code is well-formed.
Compilers usually let you stop compilation at this point. This is very useful because with it you can compile each source code file separately. The advantage this provides is that you don’t need to recompile everything if you only change a single file.
The produced object files can be put in special archives called static libraries, for easier reusing later on.
It’s at this stage that “regular” compiler errors, like syntax errors or failed overload resolution errors, are reported.
The linker is what produces the final compilation output from the object files the compiler produced. This output can be either a shared (or dynamic) library (and while the name is similar, they haven’t got much in common with static libraries mentioned earlier) or an executable.
It links all the object files by replacing the references to undefined symbols with the correct addresses. Each of these symbols can be defined in other object files or in libraries. If they are defined in libraries other than the standard library, you need to tell the linker about them.
At this stage the most common errors are missing definitions or duplicate definitions. The former means that either the definitions don’t exist (i.e. they are not written), or that the object files or libraries where they reside were not given to the linker. The latter is obvious: the same symbol was defined in two different object files or libraries.
That’s the theory, by now you probably know how you compile and execute a single C++ file:
gcc -o hello hello.cpp
Now, if you have multiple files you need to compile you need to cram them into a single command and then quickly spin out of control, since the order on which you define your files might produce compilation errors of missing symbols
So, we are going to jump a bit ahead and talk about CMake.
CMake takes care of many things for you: building, packaging, testing, etc. It’s like some parts of npm for the c++ world. If you want to write C++ for android you will use CMake and more specifically a
CMakeLists.txt that will define your compilation process
CMake is not used on iOS.
Since we are trying to be practical, you care about the
CMakeList.txt file, here is an example:
// Tell which version of CMake is required
// Check your android version, since they come bundled with the build-tools
set (CMAKE_VERBOSE_MAKEFILE ON)
// set the version of C++ you are going to use
set (CMAKE_CXX_STANDARD 11)
// Include all the directories with .cpp files that will need to be compiled
// create a library "sequel", which needs to compile all the following files
// link the following libraries together
// in this case I link android specific libraries and logging library
// so I can log to the android console from my CPP code
// on iOS this is not necessary (iOS doesn't even use CMakeLists)
target_link_libraries(sequel android log)
CMake is a world on it’s own, the wikipedia article gives a good high level overview, but if you are developing your JSI module this should be more than enough: just include all your header and cpp files in your CMakeLists and link any android libraries as necessary
Let’s go back to some of the basic stuff, you now know: how to write C++, how to compile C++, the question is now, how do you run C++ on iOS or Android? Is this some new RN-only feature?
Obj-C is actually a subset/compatible with C++ code… it’s just kinda weird. You have obj-c files (
.m) and if you change their extension (
.mm) you can use C++ code inside of the obj-c code, this also means you don’t need to set up any tooling to compile C++ code.
Just put your
cpp files in your project and you can include them just fine.
Android is a bit different, since Android works with Java/Kotlin, it does not produce binary code and cannot interact with native code written in C++. Therefore you need a translation/interaction layer called JNI.
Here is an example of JNI code that exposes a couple of functions to the Java (which is necessary for you to register your JSI callbacks)
extern "C" JNIEXPORT void JNICALL
Java_com_reactnativequicksqlite_SequelModule_initialize(JNIEnv *env, jclass clazz, jlong jsiPtr, jstring docPath)
const char *docPathString = (env)->GetStringUTFChars(docPath, &isCopy);
installSequel(*reinterpret_cast<facebook::jsi::Runtime *>(jsiPtr), docPathString);
extern "C" JNIEXPORT void JNICALL
Java_com_reactnativequicksqlite_SequelModule_destruct(JNIEnv *env, jclass clazz)
You can see your function names need to have a defined structure:
java_ = expose this to the java side
com_reactnativequicksqlite = name of the package that will be created for you
SequelModule = Module name
initialize = final function name
Like all things Android/Google the documentation is piss poor and written by robots, but if you are only interested in writing JSI module, the two functions above are all you will need
Another interesting pain point, is that you cannot just use any C++ dynamic library on android, android has it’s own flavor of dynamic libraries (.AAR) if you know how to generate them… you are a god, if not you have to rely on the ones published by google… there is 2 of them, I opted for the easy path and completely included the sqlite.c code in my library
To be honest compilation and native toolchains are so f**** complicated I cannot dive deeper without confusing you even more (even I’m confused until this day), however feel free to reach out if you need some other point explained.
Now, if you are about to embark into writing your own JSI module I can give you some useful pointers here
I tried to set up code for developing my own JSI module, there is a plugin works well enough, but does not correctly resolve the native dependencies and the compilation chain, so all you will get is errors saying it cannot find the header files.
You could try to manually pass the include paths to vscode somehow to try to guide it into correctly resolving the dependencies… but this was too much work for me because:
XCode is a terrible code editor, period. But it already knows how iOS/C++ works, if you open your project in it, it will immediately pick up your C++ files, autocompletion works, error detection (before compilation) works… so I just went with this. You can even manually format your code… if that is some consolation.
Open your cpp files with the quick-open tool (Cmd + Shift + O)
Autocompletion, error detection, etc. works
If you are a big android fan you can probably make it work, but once I had the iOS version of my JSI package working, the only code I needed was the glue to compile and link the library to the android side of things, so here you can use whatever you want to write java/kotlin.