Native C++ Module
Learn how to add a native C++ module to your application to access platform-specific APIs and maximize performance for computationally intensive tasks.
Overview
The native C++ module is a dynamically linked shared object that can be loaded in the main process and used for direct access to C++ libraries and platform-specific APIs that are not available through Node.js.
Use a native module when you need:
- Performance-critical operations. Computationally intensive tasks such as image processing, video encoding, cryptography, or complex mathematical calculations that benefit from C++ performance.
- Platform-specific APIs. Direct access to operating system APIs that are not exposed through Node.js and MōBrowser API.
- Third-party C++ libraries. Integration with existing C++ libraries.
- Hardware access. Low-level access to hardware devices or peripherals.
Most applications do not need a native module. MōBrowser API and built-in Node.js APIs cover the majority of use cases.
Creating a project with a native module
To create a project that includes a native module, run the scaffolding tool and select the option to include the native module when prompted:
npm create mobrowser-app@latest
When asked “Do you want to add a native C++ module?”, select “Yes”.
After the project is created, install the dependencies:
npm install
Project structure
A project with a native C++ module includes additional files and directories:
.vscode/
assets/
resources/
src/
├── main/
├── native/
├── main.cc
├── proto/
├── greet.proto
├── renderer/
CMakeLists.txt
mobrowser.conf.json
package.json
tsconfig.json
vite.config.tsCalling C++ from TypeScript
The native module uses Protocol Buffers (Protobuf) to define a type-safe interface between TypeScript and C++. The workflow is:
- Define messages and services in
.protofiles in thesrc/proto/directory. - Generate code for both TypeScript and C++ from the
.protofiles. The code generator runs automatically when you build the project. - Implement services in C++ by inheriting from the generated service classes.
- Call C++ functions from TypeScript using the generated services.
Let’s go through the workflow step by step in the sections below.
Prerequisites
To compile the native C++ module, you need to install C++ compiler in your environment.
- Windows 10 (64-bit) or later.
- Install Microsoft C++ Build Tools. Make sure you select the Desktop development with C++ workload during installation.
- macOS 14 (Apple Silicon) or later.
- Install Command Line Tools using
xcode-select --install.
- Ubuntu 22.04 (64-bit) or later.
- Install GCC that supports C++20 or later:
sudo apt install build-essential
Defining Protobuf messages and services
The src/proto/ directory contains .proto files. In these files you can define:
- Messages: Data structures that can be passed between TypeScript and C++.
- Services: RPC interfaces that define the functions you can call.
Example of the src/proto/greet.proto file:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
// Message definition.
message Person {
string name = 1;
}
// Service definition.
service GreetService {
rpc SayHello (Person) returns (google.protobuf.StringValue);
}
Generating code
To generate code for TypeScript and C++, run the following command:
npm run mobrowser gen
The code generator will generate the code for TypeScript and C++ based on the .proto files in the src/proto/ directory.
- The generated TypeScript code will be placed in the
src/main/gendirectory. - The generated C++ code will be placed in the
src/native/gendirectory.
.vscode/
assets/
resources/
src/
├── main/
├── gen/
├── native/
├── gen/
├── proto/
├── renderer/
CMakeLists.txt
mobrowser.conf.json
package.json
tsconfig.json
vite.config.tsImplementing services in C++
In the src/native/main.cc file, implement the services by inheriting from the generated service classes and register them in the launch() function:
#include <iostream>
#include "rpc.h"
#include "gen/greet.rpc.h"
using google::protobuf::StringValue;
using mo::rpc::Callback;
class GreetServiceImpl : public GreetService {
public:
void SayHello(const Person* person,
Callback<StringValue> done) override {
StringValue response;
response.set_value("Hello, " + person->name() + "!");
std::move(done).Complete(response);
}
};
void launch() {
mo::rpc::RegisterService(new GreetServiceImpl());
}
Calling C++ functions
Now you can import the generated code and call the service in src/main/index.ts:
import { app } from '@mobrowser/api';
import { native } from './gen/native';
const response = await native.greet.SayHello({ name: 'John Doe' })
console.log(response.value) // Hello, John Doe!
Note: You can work with the native module only after the application is initialized. The native module is loaded in the main process during the application initialization.
Modifying CMakeLists.txt
You can modify the CMakeLists.txt file in the root of your project to add source files and third-party libraries to your native module.
Adding source files
# The source files of your app.
set(APP_SOURCES
src/native/main.cc
src/native/other.h
src/native/other.cc
)
Adding include directories
# Add include directories for the native module.
target_include_directories(mobrowser_lib PUBLIC
${PROJECT_SOURCE_DIR}/src/native/gen
${MOBROWSER_SDK_NATIVE_DIR}/include
${protobuf_SOURCE_DIR}/src
${protobuf_SOURCE_DIR}/third_party/utf8_range
${absl_SOURCE_DIR}
/path/to/your/library/include
)
Adding link libraries
# Add link libraries for the native module.
target_link_libraries(mobrowser_lib PRIVATE "-framework Cocoa")