Contents

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.ts

Calling C++ from TypeScript 

The native module uses Protocol Buffers (Protobuf) to define a type-safe interface between TypeScript and C++. The workflow is:

  1. Define messages and services in .proto files in the src/proto/ directory.
  2. Generate code for both TypeScript and C++ from the .proto files. The code generator runs automatically when you build the project.
  3. Implement services in C++ by inheriting from the generated service classes.
  4. 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
macOS
Linux
  • 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/gen directory.
  • The generated C++ code will be placed in the src/native/gen directory.
.vscode/
assets/
resources/
src/
├── main/
    ├── gen/
├── native/
    ├── gen/
    ├── proto/
├── renderer/
CMakeLists.txt
mobrowser.conf.json
package.json
tsconfig.json
vite.config.ts

Implementing 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
)
# Add link libraries for the native module.
target_link_libraries(mobrowser_lib PRIVATE "-framework Cocoa")