鸿蒙操作系统通过 NAPI 实现 C/C++ 库接入实战
一、背景说明
鸿蒙应用的业务逻辑通常用 ArkTS 编写,但性能敏感场景(图像处理、加解密、音视频编解码、算法计算)往往需要复用已有的 C/C++ 代码库。NAPI(Node-API 的鸿蒙实现)是 ArkTS 与 Native C/C++ 之间的标准桥梁,通过它可以把 C++ 函数封装成 ArkTS 可直接调用的模块。近期华为 MatePad Edge 预置了"融合开发引擎"、支持在电脑模式直接运行 Linux 环境,也反映出鸿蒙生态对原生 C/C++ 开发的持续加码——本文聚焦应用侧最常用的 NAPI 混合开发完整流程。
二、技术原理
NAPI 模块的本质是一个动态库(.so),加载后向 ArkTS 运行时注册一组导出函数。整体数据流如下:
ArkTS 调用 → napi_env 上下文 → 参数解析(napi_get_cb_info)
→ C/C++ 计算 → 结果包装(napi_create_*) → 返回 ArkTS 值
三个关键概念:
- napi_env / napi_value:
napi_env是当前 JS 执行环境的句柄,所有 NAPI 调用都依赖它;napi_value是 JS 值在 C 侧的抽象,字符串、数字、对象统一用它表示。 - 模块注册:C++ 侧通过
napi_property_descriptor数组声明导出的方法名与回调,再用napi_define_properties绑定到exports对象,ArkTS 侧import后即可调用。 - 异步与线程:耗时计算应使用
napi_create_async_work把任务放入 libuv 线程池,避免阻塞 ArkTS 主线程(与 TaskPool 的思路一致,只是发生在 Native 层)。
三、实现步骤
下面以 DevEco Studio 的 Native C++ 模板工程为基础,实现一个字符串反转模块 mylib,并在 UI 中调用。
1. 工程结构
entry/
├── src/main/cpp/
│ ├── CMakeLists.txt
│ ├── napi_init.cpp # NAPI 模块实现
│ └── types/libentry/
│ └── index.d.ts # ArkTS 侧类型声明
├── src/main/ets/pages/
│ └── Index.ets # UI 调用示例
└── build-profile.json5 # 模板已含 cpp 相关配置
2. C++ 模块实现(napi_init.cpp 完整代码)
#include "napi/native_api.h"
#include <algorithm>
#include <string>
// 同步版:字符串反转
static napi_value ReverseString(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
// 取出 ArkTS 传入的参数
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
size_t len = 0;
// 先取字符串长度(UTF-8 字节数)
napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
std::string input(len + 1, '\0');
napi_get_value_string_utf8(env, args[0], &input[0], len + 1, &len);
input.resize(len);
std::reverse(input.begin(), input.end());
napi_value result = nullptr;
napi_create_string_utf8(env, input.c_str(), input.size(), &result);
return result;
}
// 异步版:求和(演示 napi_create_async_work,不阻塞主线程)
struct AddonData {
napi_async_work work = nullptr;
napi_deferred deferred = nullptr;
double a = 0;
double b = 0;
double result = 0;
};
// 在工作线程执行,不能调用任何需要 env 的 JS API(result 除外)
static void ExecuteAdd(napi_env env, void *data) {
AddonData *addon = reinterpret_cast<AddonData *>(data);
double sum = 0;
for (int i = 0; i < 1000000; i++) { // 模拟耗时计算
sum += addon->a * 0.000001 + addon->b * 0.000001;
}
addon->result = addon->a + addon->b + sum * 0.0;
}
// 计算完成,回到 JS 线程回调 Promise
static void CompleteAdd(napi_env env, napi_status status, void *data) {
AddonData *addon = reinterpret_cast<AddonData *>(data);
napi_value result = nullptr;
napi_create_double(env, addon->result, &result);
napi_resolve_deferred(env, addon->deferred, result);
napi_delete_async_work(env, addon->work);
delete addon;
}
static napi_value AddAsync(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
AddonData *addon = new AddonData();
napi_get_value_double(env, args[0], &addon->a);
napi_get_value_double(env, args[1], &addon->b);
// 创建 Promise,把 deferred 存起来用于稍后 resolve
napi_value promise = nullptr;
napi_create_promise(env, &addon->deferred, &promise);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "addAsync", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, ExecuteAdd,
CompleteAdd, addon, &addon->work);
napi_queue_async_work(env, addon->work);
return promise;
}
// 模块注册信息
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"reverseString", nullptr, ReverseString, nullptr, nullptr, nullptr, napi_default, nullptr},
{"addAsync", nullptr, AddAsync, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry", // ArkTS 侧 import 的模块名
.nm_priv = nullptr,
.reserved = {0},
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
napi_module_register(&demoModule);
}
3. CMakeLists.txt(完整代码)
cmake_minimum_required(VERSION 3.5.0)
project(myapp)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${NATIVERENDER_ROOT_PATH})
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
注意:target_link_libraries 链接的 libace_napi.z.so 是系统提供的 NAPI 运行库,不需要额外下载。
4. ArkTS 类型声明(index.d.ts)
export const reverseString: (input: string) => string;
export const addAsync: (a: number, b: number) => Promise<number>;
5. UI 调用示例(Index.ets 完整代码)
import { reverseString, addAsync } from 'libentry.so';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State inputText: string = 'Hello HarmonyOS';
@State result: string = '';
build() {
Column({ space: 16 }) {
Text('NAPI 混合开发示例')
.fontSize(22)
.fontWeight(FontWeight.Bold)
TextInput({ text: this.inputText })
.width('90%')
.onChange((value: string) => {
this.inputText = value;
})
Button('同步调用 reverseString')
.width('90%')
.onClick(() => {
this.result = reverseString(this.inputText);
})
Button('异步调用 addAsync(3, 4)')
.width('90%')
.onClick(() => {
addAsync(3, 4).then((sum: number) => {
promptAction.showToast({ message: `计算结果:${sum}` });
}).catch((err: Error) => {
promptAction.showToast({ message: `出错:${err.message}` });
});
})
Text(`结果:${this.result}`)
.fontSize(18)
.fontColor(Color.Gray)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
运行后点击第一个按钮,文本区显示 SOsnomyraH olleH;点击第二个按钮,Toast 弹出 计算结果:7,且主线程不卡顿。
四、应用场景示例
把图像降噪、国密加解密等已有 C 库接入鸿蒙应用时,只需在 napi_init.cpp 中 #include 原始头文件,在 Execute 回调里调用库函数,再把结果包装成 napi_value 返回。例如接入一个现成的哈希函数:
static napi_value Sha256Hex(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
size_t len = 0;
napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
std::string input(len + 1, '\0');
napi_get_value_string_utf8(env, args[0], &input[0], len + 1, &len);
input.resize(len);
std::string hex = MySha256Hex(input); // 来自已有 C 库
napi_value result = nullptr;
napi_create_string_utf8(env, hex.c_str(), hex.size(), &result);
return result;
}
五、技术要点
- 线程安全:Execute 回调运行在 libuv 工作线程,里面不能创建/操作 JS 对象,需要的数据在进入前拷贝进结构体,结果在 Complete 回调(JS 线程)里再包装。
- 同步接口的取舍:小数据量用同步版(延迟低);超过几毫秒的计算一律用
napi_create_async_work,否则会阻塞 ArkTS 主线程造成掉帧。 - 内存管理:
napi_create_async_work创建的对象要用napi_delete_async_work释放;new出来的上下文在 Complete 里delete,避免泄漏。 - 错误处理:参数类型不符时
napi_get_value_double等会返回非napi_ok,可用napi_throw_type_error抛给 ArkTS 侧的catch:
if (napi_get_value_double(env, args[0], &addon->a) != napi_ok) {
napi_throw_type_error(env, nullptr, "参数必须是 number");
return nullptr;
}
- ABI 限制:模拟器与真机的 so 不通用,hvigor 会按
abiList(arm64-v8a / x86_64)分别编译,发布前确认目标设备 ABI 已包含在build-profile.json5中。
六、扩展建议
需要把复杂的 JS 对象传给 C++ 时,可使用 napi_get_named_property 逐字段读取,或用 napi_unwrap 配合 napi_wrap 实现 C++ 对象与 JS 对象的生命周期绑定:
// 把 C++ 对象绑定到 JS 对象,GC 时自动调用 finalize 释放
napi_wrap(env, jsObj, nativePtr,
[](napi_env env, void *data, void *hint) { delete reinterpret_cast<MyObj *>(data); },
nullptr, nullptr);
跨设备的场景下,还可以把 Native 模块与分布式能力结合——两端各自跑同一套 C++ 算法,中间用分布式数据对象同步结果。
结语
本文完整实现了一个含同步与异步接口的 NAPI 模块:C++ 侧约 100 行,ArkTS 侧调用只需一个 import。掌握参数解析、Promise 包装和异步工作队列这三板斧后,绝大多数已有 C/C++ 库都能低成本接入鸿蒙应用,兼顾性能与生态复用。
更多推荐



所有评论(0)