目录

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.

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

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

Workflow 

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. Provide the C++/TypeScript service implementation by inheriting from the generated service classes.
  4. Invoke the C++/TypeScript service using the generated functions.

Let’s go through the workflow step by step in the sections below.

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

Calling C++ from TypeScript 

In src/native/main.cc, 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());
}

In src/main/index.ts, you need to import the generated code and call the C++ service from TypeScript:

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.

Calling TypeScript from C++ 

In src/main/index.ts, you need to implement the service by inheriting from the generated service classes and register it using the native.registerService() function:

import { native } from './gen/native';
import { GreetServiceDescriptor } from './gen/native_service';
import { Person } from './gen/native/greet';

native.registerService(GreetServiceDescriptor, {
  async SayHello(person: Person) {
    return { value: `Hello, ${person.name}!` };
  },
})

In src/native/main.cc, you can call the TypeScript service from C++ code:

#include <iostream>
#include "gen/greet.rpc.h"

void launch() {
  Person person;
  person.set_name("John Doe");
  mo::rpc::greet.SayHello(person, [](mo::rpc::Result<StringValue> result) {
    if (auto value = result.value()) {
      std::cout << value->value() << std::endl;
    }
  });
}

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")