🎯Node API Part-7 : Creating an ArkTS Runtime in Native Threads Using Node-API on HarmonyOSNex
Introduction
💡 As HarmonyOS continues to evolve, the need to bridge native C++ performance with the flexibility of ArkTS (Ark TypeScript) becomes increasingly essential — especially in high-performance or multithreaded scenarios. While ArkTS powers the UI and logic layers of many HarmonyOS applications, native modules are often indispensable for hardware-level operations or concurrent processing.
However, a challenge arises: ArkTS runtime environments are not created automatically in native threads. Without an active runtime, developers cannot directly execute ArkTS functions from native code. Thankfully, HarmonyOS offers a solution via the Node-API (NAPI). In this article, we’ll walk through how to manually create an ArkTS runtime within a native C++ thread, dynamically load an ArkTS module, and invoke functions — bringing together the best of both worlds.
Creating the Runtime: The Core Workflow
To enable communication between native and ArkTS code, we follow a structured approach:
- Register a Native Module: Declare a native method using Node-API and bind it to the createArkRuntime function.
- Use C++ Threads: Create a new pthread that hosts the runtime.
- Initialize ArkTS Runtime: Use napi_create_ark_runtime() inside the thread to create a runtime environment.
- Dynamically Load ETS Modules: Load an ETS module using napi_load_module_with_info().
- Invoke Functions: Call ArkTS-exported functions such as a Logger() using napi_call_function().
- Clean Up: Destroy the runtime after execution using napi_destroy_ark_runtime().
This pattern enables native modules to safely and efficiently execute ArkTS logic even in multi-threaded contexts.
⚙️ Step-by-Step Implementation/h3>
1. 📁 API Declaration in index.d.ts
export const createArkRuntime: () => object;
API Interface Declaration
2. 📁 Main Logics in create_ark_runtime.cpp
#include <pthread.h>
#include "napi/native_api.h"
// Thread function to create and manage the ArkTS runtime environment
static void *CreateArkRuntimeFunc(void *arg) {
// 1. Create the ArkTS runtime environment
napi_env env;
napi_status ret = napi_create_ark_runtime(&env);
if (ret != napi_ok) {
return nullptr; // Failed to create runtime
}
// 2. Load a custom module from the specified path and bundle name
napi_value objUtils;
ret = napi_load_module_with_info(env, "entry/src/main/ets/pages/ObjectUtils", "com.huawei.myapplication/entry",
&objUtils);
if (ret != napi_ok) {
return nullptr; // Failed to load module
}
// 3. Access a named export "Logger" from the loaded module
napi_value logger;
ret = napi_get_named_property(env, objUtils, "Logger", &logger);
if (ret != napi_ok) {
return nullptr; // Failed to get "Logger" property
}
// 4. Call the "Logger" function (assumed to log something)
ret = napi_call_function(env, objUtils, logger, 0, nullptr, nullptr);
// 5. Destroy the ArkTS runtime environment after use
ret = napi_destroy_ark_runtime(&env);
return nullptr;
}
// Native callback exposed to JS that creates a thread to run ArkTS logic
static napi_value CreateArkRuntime(napi_env env, napi_callback_info info) {
pthread_t tid;
pthread_create(&tid, nullptr, CreateArkRuntimeFunc, nullptr); // Start thread
pthread_join(tid, nullptr); // Wait for thread to complete
return nullptr;
}
EXTERN_C_START
// Initialization function called when the module is loaded
static napi_value Init(napi_env env, napi_value exports) {
// Define the "createArkRuntime" function to be exported to JS
napi_property_descriptor desc[] = {
{"createArkRuntime", nullptr, CreateArkRuntime, nullptr, nullptr, nullptr, napi_default, nullptr}};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
// Define the native module and register it with Node-API
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},
};
// Register the native module when the shared object is loaded
extern "C" __attribute__((constructor)) void RegisterQueueWorkModule() {
napi_module_register(&nativeModule);
}
create_ark_runtime.cpp
3. Call for creating the environment in ArkTS
import testNapi from 'libentry.so';
@Entry
@Component
struct Index {
@State message: string = 'Create Ark Runtime';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
testNapi.createArkRuntime();
})
}
.width('100%')
}
.height('100%')
}
}
Index.ets
4. Run in parallel ArkTS Script on the new thread
export function Logger() {
console.log("Console Log");
}
ObjectUtil.ets
5. Set the Config in CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(NDKBasics)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED create_ark_runtime.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)
CMakeLists.txt
✅ Key Benefits
Fine-Grained Runtime Control
Manually managing ArkTS runtimes allows developers to control when and how the JavaScript engine is initialized and destroyed — leading to precise resource management.Multithreaded Execution Support
By creating the ArkTS runtime in a separate pthread, native code can execute ArkTS logic asynchronously without blocking the main thread.Seamless Native-ArkTS Integration
Dynamically invoking ArkTS modules from native C++ bridges the performance of native code with the flexibility of TypeScript logic.Modular and Reusable Architecture
ArkTS modules like Logger can be reused and invoked from multiple native threads or contexts, encouraging code modularity and separation of concerns.Improved Performance for Background Tasks
Background operations (e.g., logging, monitoring, telemetry) can be offloaded to ArkTS modules running inside isolated runtimes, improving main thread performance.Dynamic Loading of Logic at Runtime
ETS modules can be loaded and executed dynamically, enabling flexible logic dispatch and potentially enabling plugin-like architectures.HarmonyOS Compliance and Safety
Using official Node-API functions like napi_create_ark_runtime and napi_destroy_ark_runtime ensures compatibility with HarmonyOS runtime constraints and resource limits (e.g., 16 runtimes per process).
Conclusion
✅ Bridging ArkTS and native C++ code in HarmonyOS isn’t just possible — it’s powerful. By creating a dedicated ArkTS runtime inside a native thread using Node-API, developers gain fine-grained control over when and how ArkTS logic is executed. This approach opens doors for performance optimization, background processing, and dynamic module management.
Whether you’re building system-level services or crafting high-performance apps, integrating ArkTS and C++ using this runtime strategy empowers you to scale efficiently within the HarmonyOS ecosystem.
Keep in mind that a maximum of 16 runtime environments per process is supported — design accordingly to ensure stability.
If you’re ready to take full advantage of HarmonyOS’s hybrid architecture, this is a technique worth adding to your toolkit.
📚 Resources
The OpenCms demo, brought to you by Alkacon Software.developer.huawei.com
The OpenCms demo, brought to you by Alkacon Software.developer.huawei.com
The OpenCms demo, brought to you by Alkacon Software.developer.huawei.com
Top comments (0)