Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext

Read the original article:Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext

Introduction

Welcome to our Node-API series! Ever hit a performance wall in your ArkTS app, especially with comp…


This content originally appeared on DEV Community and was authored by HarmonyOS

Read the original article:Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext

Introduction

Welcome to our Node-API series! Ever hit a performance wall in your ArkTS app, especially with complex calculations or games? That’s where Node-API steps in. It’s how HarmonyOSNext lets your ArkTS/JS code talk directly to super-efficient C/C++ modules. Let’s Start!

🎯 Why Combine Native C++ with ArkTS?
ArkTS is powerful and easy for HarmonyOSNext app development. But for compute-heavy tasks — like games or physics simulations — pure ArkTS might not cut it. That’s where your optimized C++ code comes in handy. Wrapping it for ArkTS lets you speed up your app without rewriting everything.

🔍 What is Node-API?
HarmonyOSNext Node-API bridges ArkTS/JS and native C/C++ modules with a stable, cross-platform API. Developed based on Node.js 12.x LTS Node-API, it provides a consistent set of APIs that can be used across different operating systems.

NOTE: For details about the differences between HarmonyOSNext Node-API and Node.js 12.x LTS Node-API, refer to the official Node-API documentation.

Node-API offers several key benefits:

  • Seamless Native Module Integration: Easily import native modules into ArkTS.
  • Efficient Resource Management: Cleanly manage memory and object lifecycles.
  • High-Performance Calls: Call native C/C++ methods from ArkTS with great performance.
  • Access to Core Capabilities: Encapsulate I/O, CPU-intensive tasks, and OS underlying capabilities, exposing them as ArkTS/JS interfaces.
  • Framework Layer Extension: Enables the system to expose rich module functions from the framework layer to upper-layer applications through ArkTS/JS APIs.
  • Improved Application Efficiency: Encapsulate core logic in C/C++ and use it with ArkTS/JS APIs to boost your application’s execution efficiency.

📊 Node-API Architecture

Understanding the layers and interactions of Node-API is crucial for effective integration.

  • Native module: A module developed using Node-API and imported into ArkTS.
  • Node-API: Implements the core logic for interaction between ArkTS and C/C++ code.
  • ModuleManager: Manages native modules, including loading and locating them.
  • ScopeManager: Manages the lifecycle of napi_value (ArkTS/JS values).
  • ReferenceManager: Manages the lifecycle of napi_ref (persistent references to ArkTS/JS objects).
  • Native engine: An abstraction layer for the ArkTS engine, standardizing - the API behavior at the Node-API level.
  • ArkCompiler ArkTS Runtime: The runtime environment where ArkTS code executes. 🔄 Key Interaction Process of Node-API The interaction between ArkTS and C++ primarily consists of two steps:

Initialization:

  • When a native module is imported into ArkTS, the ArkTS engine calls the ModuleManager.
  • The ModuleManager then loads the module's .so file (shared library) and its dependencies.
  • Upon the module’s first load, module registration is triggered.
  • Finally, the method properties defined by the module are embedded into an exports object, and this object is returned to ArkTS.

Invocation:

  • When an ArkTS method is called using the exports object, the ArkTS engine locates and invokes the corresponding embedded C/C++ method.

🛠️ How to Wrap Native Objects as ArkTS Objects (Wrap/Unwrap)
To use a native C++ class in ArkTS, you need to wrap it. This links the native C++ object to an ArkTS object, letting ArkTS manage its lifecycle. Here’s where napi_wrap and napi_unwrap come into play.

Let's experience Step By Step with DevEcoStudio :

1- Create a project (File — New — Create Project — Native C++)


2- Add abiFilters config at entry-level build-profile.json5

{
  "apiType": "stageMode",
  "buildOption": {
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "arguments": "",
      "cppFlags": "",
      "abiFilters": [
        "arm64-v8a",
        "x86_64"
      ]
    }
  },
  "buildOptionSet": [
    {
      "name": "release",
      "arkOptions": {
        "obfuscation": {
          "ruleOptions": {
            "enable": false,
            "files": [
              "./obfuscation-rules.txt"
            ]
          }
        }
      },
      "nativeLib": {
        "debugSymbol": {
          "strip": true,
          "exclude": []
        }
      }
    },
  ],
  "targets": [
    {
      "name": "default"
    },
    {
      "name": "ohosTest",
    }
  ]
}

build-profile.json5

3- Set the interface class (Index.d.ts)

export class MyObject {
  constructor(arg: number);
  plusOne: () => number;

  public get value();
  public set value(newVal: number);
}

index.d.ts

4- Set napi_init.cpp class

// napi_init.cpp

#include "napi/native_api.h"
#include "hilog/log.h"

// A native C++ class that will be wrapped into an ArkTS (JavaScript) object
class MyObject {
 public:
  // Initializes the class and exports it to ArkTS
  static napi_value Init(napi_env env, napi_value exports);
  // Destructor called when the native object is garbage collected
  static void Destructor(napi_env env, void* nativeObject, void* finalize_hint);

 private:
  // Constructor and Destructor
  explicit MyObject(double value_ = 0);
  ~MyObject();

  // Class methods accessible from ArkTS
  static napi_value New(napi_env env, napi_callback_info info);
  static napi_value GetValue(napi_env env, napi_callback_info info);
  static napi_value SetValue(napi_env env, napi_callback_info info);
  static napi_value PlusOne(napi_env env, napi_callback_info info);

  // Member variables
  double value_;           // internal numeric value
  napi_env env_;           // N-API environment pointer
  napi_ref wrapper_;       // Reference to the wrapped JS object
};

// Global reference to the constructor function, used for instance creation
static thread_local napi_ref g_ref = nullptr;

// Constructor implementation
MyObject::MyObject(double value)
    : value_(value), env_(nullptr), wrapper_(nullptr) {}

// Destructor implementation
MyObject::~MyObject()
{
  napi_delete_reference(env_, wrapper_);
}

// This function is called when the JS object is garbage collected
void MyObject::Destructor(napi_env env, void* nativeObject, [[maybe_unused]] void* finalize_hint)
{
  OH_LOG_INFO(LOG_APP, "MyObject::Destructor called");
  delete reinterpret_cast<MyObject*>(nativeObject);
}

// Initializes and exports the MyObject class to the ArkTS runtime
napi_value MyObject::Init(napi_env env, napi_value exports)
{
  // Define class methods and properties exposed to ArkTS
  napi_property_descriptor properties[] = {
      { "value", 0, 0, GetValue, SetValue, 0, napi_default, 0 },
      { "plusOne", nullptr, PlusOne, nullptr, nullptr, nullptr, napi_default, nullptr }
  };

  napi_value cons;
  napi_define_class(env, "MyObject", NAPI_AUTO_LENGTH, New, nullptr, 2, properties, &cons);

  // Store a reference to the constructor for later use
  napi_create_reference(env, cons, 1, &g_ref);

  // Export the class as a property of the module
  napi_set_named_property(env, exports, "MyObject", cons);
  return exports;
}

// Export function that will be called when the module is loaded
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    MyObject::Init(env, exports);
    return exports;
}
EXTERN_C_END

// Native module definition
static napi_module nativeModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = nullptr,
    .reserved = { 0 },
};

// This function is automatically called when the native module is loaded
extern "C" __attribute__((constructor)) void RegisterObjectWrapModule()
{
    napi_module_register(&nativeModule);
}

// Class constructor callable from ArkTS
napi_value MyObject::New(napi_env env, napi_callback_info info)
{
  OH_LOG_INFO(LOG_APP, "MyObject::New called");

  napi_value newTarget;
  napi_get_new_target(env, info, &newTarget);

  if (newTarget != nullptr) {
    // Called using `new MyObject(...)`
    size_t argc = 1;
    napi_value args[1];
    napi_value jsThis;
    napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr);

    double value = 0.0;
    napi_valuetype valuetype;
    napi_typeof(env, args[0], &valuetype);
    if (valuetype != napi_undefined) {
      napi_get_value_double(env, args[0], &value);
    }

    // Create the native object
    MyObject* obj = new MyObject(value);
    obj->env_ = env;

    // Wrap native C++ object with JS object
    napi_status status = napi_wrap(env,
                                   jsThis,
                                   reinterpret_cast<void*>(obj),
                                   MyObject::Destructor,
                                   nullptr,
                                   &obj->wrapper_);

    // Check for wrap failure
    if (status != napi_ok) {
      OH_LOG_INFO(LOG_APP, "Failed to bind native object to js object, return code: %{public}d", status);
      delete obj;
      return jsThis;
    }

    // Convert reference to weak to avoid memory leaks
    uint32_t refCount = 0;
    napi_reference_unref(env, obj->wrapper_, &refCount);

    return jsThis;
  } else {
    // Called as a function: `MyObject(...)` instead of `new MyObject(...)`
    size_t argc = 1;
    napi_value args[1];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // Create a new instance using the constructor reference
    napi_value cons;
    napi_get_reference_value(env, g_ref, &cons);
    napi_value instance;
    napi_new_instance(env, cons, argc, args, &instance);

    return instance;
  }
}

// Getter: Returns the value_ field to ArkTS
napi_value MyObject::GetValue(napi_env env, napi_callback_info info)
{
  OH_LOG_INFO(LOG_APP, "MyObject::GetValue called");

  napi_value jsThis;
  napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);

  // Unwrap native object from JS
  MyObject* obj;
  napi_unwrap(env, jsThis, reinterpret_cast<void**>(&obj));

  // Create JS number from native value
  napi_value num;
  napi_create_double(env, obj->value_, &num);

  return num;
}

// Setter: Updates the native value_ field from ArkTS
napi_value MyObject::SetValue(napi_env env, napi_callback_info info)
{
  OH_LOG_INFO(LOG_APP, "MyObject::SetValue called");

  size_t argc = 1;
  napi_value value;
  napi_value jsThis;

  napi_get_cb_info(env, info, &argc, &value, &jsThis, nullptr);

  // Unwrap native object from JS
  MyObject* obj;
  napi_unwrap(env, jsThis, reinterpret_cast<void**>(&obj));

  // Set internal value_
  napi_get_value_double(env, value, &obj->value_);

  return nullptr;
}

// Adds 1 to the internal value_ and returns it
napi_value MyObject::PlusOne(napi_env env, napi_callback_info info)
{
  OH_LOG_INFO(LOG_APP, "MyObject::PlusOne called");

  napi_value jsThis;
  napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);

  // Unwrap native object from JS
  MyObject* obj;
  napi_unwrap(env, jsThis, reinterpret_cast<void**>(&obj));

  // Increment value_ and return
  obj->value_ += 1;
  napi_value num;
  napi_create_double(env, obj->value_, &num);

  return num;
}

napi_init.cpp

  • napi_wrap: Binds the ArkTS object to the native C++ instance. This function establishes the connection, allowing the ArkTS runtime to manage the native object.
  • napi_unwrap: Retrieves the native C++ object from its wrapped ArkTS counterpart. This is essential when you need to access or manipulate the native C++ instance from within your native methods.

Don't forget to set the MakeList.txt config file

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(NDKBasics)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

add_definitions("-DLOG_DOMAIN=0x0000")
add_definitions("-DLOG_TAG=\"testTag\"")

add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)

Main Config File
💻 It is time to use the Wrapped Object in ArkTS
Once wrapped, your native C++ object can be used directly within ArkTS code, as if it were a native ArkTS object:

import hilog from '@ohos.hilog';
import { MyObject } from 'libentry.so';

let object: MyObject = new MyObject(0);
object.value = 1023;

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {

            hilog.info(0x0000, 'testTag', 'MyObject value after set: %{public}d', object.value);
            hilog.info(0x0000, 'testTag', 'MyObject plusOne: %{public}d', object.plusOne());

            this.message = object.value.toString() + " " + object.plusOne()
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

index.ets (Only ArkTS Class on project — Caller Script)

📝 Summary

  • Node-API is a powerful bridge between ArkTS and C++, allowing high-performance native module integration.
  • Use napi_wrap and napi_unwrap to bind native objects with ArkTS objects and retrieve them.
  • The Destructor function is crucial for memory safety, ensuring native resources are released when the ArkTS wrapper is garbage collected.
  • This approach helps you integrate high-performance native modules into your ArkTS apps effortlessly.

🎁 Conclusion

Wrapping native C++ modules with Node-API unlocks a world of performance and flexibility for your HarmonyOSNext projects. I hope this comprehensive guide helps you get started quickly.

References:

https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V5/use-sendable-napi-V5?source=post_page-----0a743f5cc175---------------------------------------

https://forums.developer.huawei.com/forumPortal/en/topic/0203190036169712060

Written by BunyaminEymenAlagoz


This content originally appeared on DEV Community and was authored by HarmonyOS


Print Share Comment Cite Upload Translate Updates
APA

HarmonyOS | Sciencx (2025-08-27T12:46:25+00:00) Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext. Retrieved from https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/

MLA
" » Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext." HarmonyOS | Sciencx - Wednesday August 27, 2025, https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/
HARVARD
HarmonyOS | Sciencx Wednesday August 27, 2025 » Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext., viewed ,<https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/>
VANCOUVER
HarmonyOS | Sciencx - » Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/
CHICAGO
" » Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext." HarmonyOS | Sciencx - Accessed . https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/
IEEE
" » Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext." HarmonyOS | Sciencx [Online]. Available: https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/. [Accessed: ]
rf:citation
» Node-API Part-1: Introduction to Node-API & Wrapping Native C++ Objects in ArkTS on HarmonyOSNext | HarmonyOS | Sciencx | https://www.scien.cx/2025/08/27/node-api-part-1-introduction-to-node-api-wrapping-native-c-objects-in-arkts-on-harmonyosnext/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.