鸿蒙js调用c++详解

HarmonyOS XTS框架与 JS 调用 C++ 接口详解

XTS 框架的核心概念

HarmonyOS XTS(X Test Suite)是 OpenHarmony/HarmonyOS 提供的一套生态认证测试框架,包含多种测试套件以验证设备和应用的兼容性 。目前 XTS 主要包括:
• acts(应用兼容性测试套件):存放应用兼容性测试用例及配置,帮助终端设备厂商及早发现与 OpenHarmony 平台的不兼容问题,确保应用在开发过程中满足兼容性要求 。
• tools(测试工具包):存放 acts 套件相关的测试开发框架和工具,提供编写、执行测试用例的支持 。

通过 XTS 框架,开发者可以编写自动化测试用例来验证系统各子系统和接口的正确性。XTS 涵盖轻量级设备、小型系统、标准系统等不同配置的测试方案,确保不同设备上的 HarmonyOS 实现都符合统一的兼容性标准 。简而言之,XTS 是 HarmonyOS 官方测试套件集合,为软硬件兼容性保驾护航。

JS(ArkTS)调用 C++ 接口的方式

在 HarmonyOS(尤其是最新 ArkUI/ArkTS 应用开发)中,推荐使用 N-API(Node-API)机制让 JavaScript/ArkTS 与 C/C++ 代码进行互调 。N-API 是 HarmonyOS NDK(Native Development Kit)的一部分,最初源于 Node.js 的 Node-API 标准,但针对 ArkTS 运行时做了扩展,和 Node.js 的 N-API 并不完全兼容 。它提供了一组稳定的 C/C++ 接口,让开发者可以在 ArkTS/JS 代码中调用本地 C/C++ 模块,或让 C/C++ 反过来调用 ArkTS 。换言之,在 ArkTS 中 N-API 扮演了类似 JNI(Java Native Interface)的角色,充当跨语言调用的中间桥梁 。不过,与 Android 上 JS 需要通过 Java 层再用 JNI 不同,ArkTS 直接通过 N-API 与底层 C/C++ 通信,省去中间的 Java 层开销。

**JNI 与 N-API 的区别:**JNI 是 Java 虚拟机提供的原生接口,用于 Java 调用本地 C/C++ 代码。而 HarmonyOS 从鸿蒙 3.0 开始引入了 ArkTS 运行时,应用不再使用 Java UI 框架,转而使用 ArkTS/JS。此时,ArkTS 调用原生能力改用 N-API 接口,实现方式与 Node.js 插件相似  。因此在 ArkTS 应用中,一般不会直接使用 JNI;如果必须与现有 Java 接口交互,通常需在系统框架层提供对应的 ArkTS 接口,或通过原生模块间接调用(例如系统内部将 Java 接口封装为 ArkTS 可调用的 NAPI 接口)。总的来说,ArkTS/JS <-> C++ 跨语言调用首选 N-API,这套机制屏蔽了底层差异,让 ArkTS 代码像调用普通模块一样使用 C/C++ 实现的功能。

HarmonyOS Node-API 接口封装了大量系统底层能力(如文件 I/O、硬件访问、计算密集型功能等),对于性能敏感场景(如游戏、音视频处理、物理仿真等)或复用现有 C/C++ 库的场景,可以通过 N-API 将这些高效的原生代码暴露给 ArkTS 调用  。典型的使用场景包括:将系统框架层的原生能力封装为 JS 接口提供给应用,或者应用开发者将关键算法用 C/C++ 实现并通过 JS 调用以提升性能 。

**ArkTS 调用 C++ 的基本流程:**ArkTS 通过 import 导入一个 Native 模块(对应一个 .so 动态库)时,系统会加载该模块的 .so 文件,并触发 N-API 框架注册模块 。Native 模块需按照 N-API 规范导出一个模块初始化函数和模块名等信息:
• 定义一个全局的 napi_module 结构,其中包含模块名(nm_modname)和初始化函数指针(nm_register_func)等 。模块名通常对应生成的 .so 文件名,例如 “libmyModule.so” 可在 ArkTS 中通过 import myModule from ‘@ohos.myModule’; 引入(需要在 oh-package.json5 中声明名称,见下文)。
• 在模块加载时,HarmonyOS 会调用 napi_module_register(&myModuleStruct) 将模块注册到引擎,并执行我们提供的初始化函数 。在初始化函数中,我们使用 N-API 提供的接口将 C++实现的函数绑定到 JS 可调用的接口上。

**绑定 C++ 接口为 JS 可调用接口:**在模块初始化 (Init) 函数中,通常会使用 napi_define_properties 或 napi_create_function 等接口,将 C++实现的函数注册为 JS 对象的属性/方法 。例如,下方代码将三个原生函数 resultMsg、setOnCallBack、nativeToTSCallBack 绑定为 JS 导出:

// C++侧 napi_init.cpp 中的模块初始化函数
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{“resultMsg”, nullptr, resultMsg, nullptr, nullptr, nullptr, napi_default, nullptr},
{“setOnCallBack”, nullptr, setOnCallBack, nullptr, nullptr, nullptr, napi_default, nullptr},
{“nativeToTSCallBack”, nullptr, nativeToTSCallBack, nullptr, nullptr, nullptr, napi_default, nullptr}
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END

对应地,在 ArkTS 侧需要提供类型声明文件来描述这些接口,使得 TS 代码可以正常引用。通常会在工程的 entry/src/main/cpp/types/ 目录下创建一个 index.d.ts(或命名为模块名的 .d.ts)声明导出的接口签名,例如:

// ArkTS/TS侧 index.d.ts 文件
export const resultMsg: (a: number, b: number) => number;
export const setOnCallBack: (cb: (progress: number) => void) => void;
export const nativeToTSCallBack: (a: number) => void;

然后在 oh-package.json5 中将生成的 .so 库与该类型声明关联,声明包名和入口。例如:

{
“name”: “libentry.so”, // 模块对应的 .so 名称
“types”: “./index.d.ts”, // 类型定义文件路径
“version”: “1.0.0”,
“description”: “Native module for …”}
}

上述配置确保 ArkTS 在 import 此模块时能找到类型定义,并加载对应的原生库 。一旦完成这些步骤,就可以在 ArkTS 代码中像调用普通接口一样使用 C++ 实现的功能。例如:

import * as nativeModule from ‘@ohos.entry’; // 假设模块名为entry

let result = nativeModule.resultMsg(5, 3);
// result 获得 C++ 函数计算的结果 (5+3)

nativeModule.setOnCallBack(progress => {
console.log(‘Progress:’, progress);
});
nativeModule.nativeToTSCallBack(42);
// 触发 C++ 侧调用之前设置的回调,将 42 传回 ArkTS

注:在上述示例中,setOnCallBack 用于将一个 ArkTS 回调函数传递给 C++ 保存起来,而 nativeToTSCallBack 则可能由 C++ 主动调用,用于调用之前登记的回调,以实现 C++ 反向通知 JS。  这种模式常用于原生层异步任务完成后通知前端。例如,C++ 启动一个耗时任务,在任务线程结束时通过调用 JS 回调来通知 UI 更新进度。HarmonyOS N-API 提供 线程安全函数(Thread-safe function) 等机制,支持在异步线程中安全地回调 JS 函数,以避免线程冲突和死锁风险 。目前不支持在非 JS 线程上直接调用 ArkTS 接口,必须通过 napi_call_function 或线程安全函数把调用投递到 JS 引擎线程执行 。这一点务必注意,否则可能引发不可预期的行为。

相关开发工具与插件支持

HarmonyOS 为 NAPI 开发提供了良好的工具链支持,方便开发者创建和调试 JS<->C++交互模块:
• **DevEco Studio IDE:**华为提供的 HarmonyOS 集成开发环境。在最新的 DevEco Studio 版本中,可以直接创建 Native C++ 模块模板。例如,在 ArkTS 项目上右键选择 “New > Module”,然后选择 Native C++,即可添加一个原生模块 。DevEco 会自动生成所需的目录结构和配置文件(如 CMakeLists.txt、build-profile.json5 等 ),并链接 HarmonyOS 提供的 NAPI 库(通常在 CMake 中通过 target_link_libraries( PUBLIC libace_napi.z.so) 引入 )。这使开发者无需手动搭建 NDK 编译环境,开箱即用进行 JS 和 C++ 混合开发。
• NAPI 框架代码生成工具:为了降低手动编写 NAPI 模块的复杂度,OpenHarmony 社区提供了 NAPI 代码生成工具。该工具可根据开发者提供的 TypeScript 接口定义文件,一键生成 NAPI 模块的框架代码和业务代码骨架(包括 .cpp 实现、index.d.ts 声明、GN/CMake 构建文件等) 。使用生成工具,开发者无需深究 Node.js 层的细节或 JS/C++ 类型转换的繁琐步骤,只需专注于填充底层业务逻辑即可 。此工具支持多种使用方式:既可以通过命令行可执行程序运行,也提供了 VS Code 插件 和 DevEco Studio(IntelliJ 平台)插件 三种形式,开发者可根据习惯选择 。例如,在 VS Code 中安装相应插件后,指定 .ts 接口文件路径即可自动生成完整的 NAPI 模块代码框架,大大提高开发效率,避免手动书写出错  。
• **调试与测试:**DevEco Studio 支持对 C++ 代码的断点调试,方便在 JS 调用原生接口时跟踪 C++ 层的运行情况。同时,HarmonyOS 提供了一系列 XTS 测试用例 来验证 NAPI 框架本身的正确性。例如,XTS 中包含 ArkTS 模块的测试用例,用于验证跨语言调用行为是否符合预期(最近社区更新还对 XTS 的 ArkTS 测试模块进行了完善 )。开发者在编写自己的 JS-C++交互时,也应参考这些官方测试和指南,确保代码在不同设备上的兼容性。

代码示例与最佳实践

下面结合一个简单示例,展示 JS/ArkTS 调用 C++ 接口的代码结构,并总结相关最佳实践:

**示例场景:**假设需要在 ArkTS 中调用 C++ 实现的数学计算函数,并让 C++ 能回调通知 ArkTS 进度。开发步骤如下:
1. **定义 TS 接口:**在 ArkTS 工程的 cpp/types 目录下创建接口声明文件 index.d.ts,定义 JS 侧将要调用的函数签名和回调接口:

declare namespace mymodule {
function computeSum(a: number, b: number): number;
function setProgressCallback(cb: (p: number) => void): void;
function triggerCallback(val: number): void;
}
export default mymodule;

以上定义了命名空间 mymodule 下的三个接口:computeSum 进行计算并返回结果,setProgressCallback 用于注册一个进度回调,triggerCallback 则由原生侧调用,触发之前注册的回调。 确保在 oh-package.json5 中将该接口文件与待生成的原生库关联,例如 “name”: “libmymodule.so”, “types”: “./index.d.ts”。

2.	**实现 C++ 模块:**在 cpp 源码目录下编写 C++ 实现,例如 mymodule.cpp。首先包含 NAPI 的头文件(HarmonyOS NDK 提供)并定义模块初始化函数和需要导出的原生函数:

#include <napi/native_api.h>
#include <napi/native_node_api.h>
static napi_ref g_progressCallback = nullptr; // 全局引用保存JS回调

// C++实现的计算函数
static napi_value ComputeSum(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
double a, b;
napi_get_value_double(env, args[0], &a);
napi_get_value_double(env, args[1], &b);
double sum = a + b;
napi_value result;
napi_create_double(env, sum, &result);
return result;
}

// 设置回调函数:保存JS传入的回调
static napi_value SetProgressCallback(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
// 将JS回调保存为持久引用,以便后续调用
if (argc == 1) {
napi_create_reference(env, args[0], 1, &g_progressCallback);
}
return nullptr;
}

// 触发回调:由JS侧调用或原生逻辑内部调用,调用之前保存的JS回调
static napi_value TriggerCallback(napi_env env, napi_callback_info info) {
// 假设直接传入一个数值参数
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (g_progressCallback != nullptr) {
napi_value global;
napi_get_global(env, &global);
napi_value cb;
napi_get_reference_value(env, g_progressCallback, &cb);
// 调用保存的回调函数,将 args[0] 转发给它
napi_call_function(env, global, cb, 1, args, nullptr);
}
return nullptr;
}

// 模块初始化:绑定以上原生函数为 JS 接口
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{“computeSum”, nullptr, ComputeSum, nullptr, nullptr, nullptr, napi_default, nullptr},
{“setProgressCallback”, nullptr, SetProgressCallback, nullptr, nullptr, nullptr, napi_default, nullptr},
{“triggerCallback”, nullptr, TriggerCallback, nullptr, nullptr, nullptr, napi_default, nullptr}
};
napi_define_properties(env, exports, sizeof(desc)/sizeof(desc[0]), desc);
return exports;
}

// 注册模块
static napi_module myModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init, // 指定初始化函数
.nm_modname = “mymodule”, // 模块名,对应 import 名称
.nm_priv = nullptr,
.reserved = {0}
};
extern “C” attribute((constructor)) void RegisterModule() {
napi_module_register(&myModule);
}

上述 C++ 代码通过全局静态结构体注册模块信息,在模块被加载时调用 RegisterModule 来注册本模块 。初始化函数 Init 中用属性描述符数组将 C++函数导出为 JS 属性(方法)。特别地,我们通过一个 napi_ref 保存了 JS 回调函数,以便在 TriggerCallback 中进行调用演示。需要注意:如果 C++ 在其它线程想通知 JS,比如定时发送进度,需要使用 线程安全函数 napi_create_threadsafe_function 来实现,将回调封装成事件投递到 JS 事件循环,否则直接从工作线程调用 JS 函数是不被支持的 。

3.	**配置构建并运行:**确认 CMakeLists.txt 已正确添加生成 .so 库的规则和链接 NAPI 库。例如:

add_library(mymodule SHARED mymodule.cpp)
target_link_libraries(mymodule PUBLIC libace_napi.z.so) # 链接 ArkUI 提供的 NAPI 库

通过 DevEco Studio 编译打包应用后,在 ArkTS 代码中就可以使用 import mymodule from ‘@ohos.mymodule’; 来加载原生模块,并调用其中的方法:

import mymodule from ‘@ohos.mymodule’;

console.log(“Sum=”, mymodule.computeSum(1, 2)); // 输出 Sum=3

mymodule.setProgressCallback(p => {
console.log(“Progress callback from C++:”, p);
});
// … 在某处触发回调
mymodule.triggerCallback(100); // 控制台应打印 “Progress callback from C++: 100”

运行效果表明 ArkTS 成功调用了 C++ 实现的逻辑,并且 C++ 也能通过回调将数据传回 ArkTS。这种跨语言调用方式性能开销很低,适合频繁调用的小函数。但是如果需要极高频率地在 JS 和 C++ 之间通信,仍应考虑将批量操作尽量整合在 C++ 一侧完成,减少来回调用次数,以优化性能 。例如,对于大量数据处理,可在 C++ 一次性处理完毕后将结果一次性返回,而非在 JS 层用循环反复调用单个计算函数。

**最佳实践小结:**HarmonyOS 提供的 N-API 机制使 JS/ArkTS 与 C++ 的交互变得直观高效,但开发者仍需遵循一些最佳实践以确保稳定性和性能:
• **正确管理对象与内存:**注意 N-API 返回的 napi_value 句柄的作用域,及时通过 napi_open_handle_scope/napi_close_handle_scope 控制对象生命周期,避免因为未释放 JS 引用而导致内存泄漏  。对长生命周期对象,可使用 napi_create_reference 提升 scope;对临时大量对象,使用局部 scope 限定其生命周期。
• **控制跨语言调用频率:**虽然 NAPI 调用本身已做了性能优化,但每次跨语言边界仍有一定开销。对于高频、小粒度的调用,尽量合并处理或使用批量接口。比如将多个属性读写合并为一次调用,或者让 C++ 进行循环运算后一次返回结果 。减少 JS<->C++ 来回切换有助于提升整体性能。
• **异步和线程安全:**避免在 NAPI 原生函数中执行耗时操作,否则会阻塞 ArkTS 主线程。对于耗时任务,可使用 napi_create_async_work 开启后台执行,任务完成后再通过回调通知 JS。利用 napi_create_threadsafe_function 等接口确保跨线程调用的安全,将结果派发回主线程 。切勿直接在非 JS 线程调用 JS 引擎方法。
• **充分利用工具:**优先使用 NAPI 代码生成插件 来创建模块雏形,以减少手工错误 。同时参考 HarmonyOS 官方示例和 XTS 测试用例,遵循既定的项目结构(将接口声明放入 cpp/types 目录、正确配置 oh-package.json5、链接系统 NAPI 库等)确保模块可被框架正确识别加载  。

按照以上指南开发,JS/ArkTS 和 C++ 的接口交互将既高效又安全。HarmonyOS 最新版本中,ArkTS + NAPI 的组合已经相当成熟,能够满足绝大多数跨语言调用需求。当遇到系统未直接提供的能力时,可以考虑通过自定义 NAPI 模块扩展功能,在保持应用兼容性的同时,充分发挥 C/C++ 的性能优势  。

参考资料:
1. Huawei HarmonyOS Docs – “NDK Development Overview”  
2. OpenHarmony 社区博客 – “OpenHarmony——XTS子系统” 
3. 华为开发者论坛问答 – “ArkTS与C/C++跨语言调用(NAPI)”  
4. 优快云 博客 – 《HarmonyOS 应用开发:使用 NAPI 实现 C 与 ArkTS 交互》  
5. 优快云 博客 – 《OpenHarmony 系统之 NAPI 框架生成工具介绍》  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值