Skip to main content

Native Modules: C++

C++ is a good choice for a module when the code already exists in C++, or when it is shared with iOS and Android. On desktop, a C++ module is the same TurboModule as on mobile: a class that derives from a generated spec.

This page is the reference for C++ modules. For a first module, start with the introduction.

A C++ module is a library​

A C++ module lives in a library, which you create with the CLI:

npx react-native create-library AwesomeStorage --type cpp

It writes:

FilePurpose
src/NativeAwesomeStorage.tsThe TypeScript specification.
desktop/cpp/RNAwesomeStorageModule.hpp and .cppThe implementation, and the function that creates it.
desktop/CMakeLists.txtAdds the sources to the app.
react-native.config.jsDeclares platforms.desktop with cmake and cppProvider.
package.jsonThe codegenConfig that Codegen reads.
react-native.config.js
module.exports = {
dependency: {
platforms: {
desktop: {
cmake: 'desktop',
cppProvider: 'create_awesome_storage_module',
},
},
},
};

cmake is the folder with the CMakeLists.txt, and cppProvider is the C++ function that creates the module.

The generated spec class​

When the app builds, autolinking runs React Native Codegen over the library's specifications and writes <codegenConfig.name>JSI.h and <codegenConfig.name>.h under desktop/autolink/codegen/. The second header declares one spec class per specification file: src/NativeGreeting.ts defines NativeGreetingSpec.

Derive from it, passing the implementation class as the template argument:

C++
class RNModule final : public facebook::react::NativeModuleSpec<RNModule> {
public:
explicit RNModule(std::shared_ptr<CallInvoker> jsInvoker)
: NativeModuleSpec(std::move(jsInvoker)) {}

std::string greet(jsi::Runtime&, std::string name) {
return "Hello from C++, " + name + "!";
}
};

and return it from the provider function:

C++
std::shared_ptr<TurboModule> create_awesome_storage_module(
const std::string& name,
std::shared_ptr<CallInvoker> jsInvoker) {
return rngpui::provideTurboModule<RNModule>(name, std::move(jsInvoker));
}

Types​

React Native's bridging converts arguments and results, so declare the C++ type the specification maps to:

Spec typeC++ type
stringstd::string
number, Double, Floatdouble
Int32int32_t
booleanbool
T | null, an optional parameterstd::optional<T>
Array<T>std::vector<T>
Object, an object typefolly::dynamic; a named object type uses the struct Codegen generates
ArrayBufferstd::vector<uint8_t>
A callbackrngpui::Callback<...>
An enum or literal unionstd::string or double

A signature that disagrees with the specification fails to compile.

Threads​

How a method is declared in the specification decides where it runs:

Spec return typeC++ methodRuns on
A valueT name(jsi::Runtime&, args...)The JavaScript thread
voidvoid name(args...)The module's thread
Promise<T>void name(args..., rngpui::Promise<T> promise)The module's thread

A method that does not run on the JavaScript thread cannot take a JSI type, because JSI values belong to the runtime's thread.

To choose another thread, add a public constant to the class. It must be public, because the generated spec class reads T::kThread:

C++
class RNModule final : public facebook::react::NativeModuleSpec<RNModule> {
public:
static constexpr rngpui::ModuleThread kThread = rngpui::ModuleThread::Main;
// ...
};
ValueMeaning
ModuleThread::OwnA thread owned by the module. The default.
ModuleThread::MainThe GPUI main thread, for window and UI work.
ModuleThread::JSInline on the JavaScript thread. The method then takes jsi::Runtime& first.

rngpui::dispatchMain(task) runs a function on the main thread from anywhere.

Promises​

A Promise<T> method receives a promise, which may be settled from any thread. Only the first settlement counts:

C++
void getLength(std::string text, rngpui::Promise<double> promise) {
promise.resolve(static_cast<double>(text.size()));
}

void fail(rngpui::Promise<void> promise) {
promise.reject("ENOENT", "no such file"); // a code and a message
}

Events​

A spec that declares readonly onProgress: CodegenTypes.EventEmitter<number> generates a protected emitOnProgress(value) on the spec class. Any thread may call it. For a module used through NativeEventEmitter, sendEvent("name", body) does the same.

Calling the operating system​

C++ code runs in the same process as the app, so it calls the operating system's APIs directly: Cocoa from an Objective-C++ file on macOS, Win32 on Windows, and POSIX or a C library on Linux. Guard platform code with the usual #if defined(__APPLE__), _WIN32 and __linux__ checks, and add the right system libraries in CMakeLists.txt.

Sharing code with iOS and Android​

A library with C++ code can serve all three. The portable core stays as it is for the mobile platforms, and the desktop/ folder holds the desktop entry point. Declare the library's iOS and Android support as you do today; platforms.desktop is the desktop addition.