1. 项目概述:为什么要在鸿蒙Next上搞JS与C++交互?
如果你正在为鸿蒙Next开发应用,尤其是涉及到复杂计算、硬件深度访问或者性能敏感的场景,你大概率会遇到一个瓶颈:纯JavaScript(或ArkTS)的性能或能力边界。这时候,把目光投向C/C++就成了一个自然而然的选择。通过JSVM-API这座桥梁,让前端灵活的JS逻辑和后端强悍的C++模块协同工作,是鸿蒙应用进阶开发的必修课。
这不仅仅是“能调用一个C++函数”那么简单。它意味着你可以将图像识别算法、物理引擎、音视频编解码、自定义硬件驱动等重计算、高性能的模块,用C/C++高效实现,然后通过JSVM-API暴露给上层的JS/ArkTS业务层。整个应用的架构会变得更加清晰,性能瓶颈也能得到根本性的解决。最近在开发者社区里,关于“该应用已适配 HarmonyOS Next”的讨论热度很高,而一个成熟、高性能的Next应用,往往离不开这种跨语言协同的开发模式。接下来,我就以一个实际开发者的视角,带你走通从环境搭建、接口定义、编译调试到问题排查的完整流程,分享那些官方文档里不会写的“坑”和技巧。
2. 核心思路与架构设计
2.1 JSVM-API的角色定位
首先得搞清楚JSVM-API到底是什么。它不是一个新的编程语言,而是鸿蒙Native API(Native API)中专门用于实现JavaScript(或ArkTS)与C/C++代码交互的那一部分接口集合。你可以把它理解为一套“翻译规则”和“调用约定”。
当你的JS代码调用一个标记为native的方法时,鸿蒙的运行时(ARK Runtime)并不会直接在JS引擎里执行它,而是通过JSVM-API,将调用请求“转发”到你已经预先编译好的、包含对应C/C++实现的Native库(.so文件)中。JSVM-API负责处理两者之间巨大的鸿沟:数据类型的转换(比如把JS的number转成C的double或int32_t)、内存管理(谁申请,谁释放,如何避免泄漏)、以及异常处理。
为什么是“JSVM”?这里的“VM”指的是管理JavaScript执行的虚拟机(在鸿蒙中是ARK引擎)。JSVM-API就是给这个虚拟机提供的、用于与外部Native世界通信的底层接口。所以,你的C++代码实际上是在和JS虚拟机打交道,而不是直接操作JS对象。
2.2 两种交互模式:同步与异步
在设计交互层时,首先要根据场景决定模式:
同步调用:JS线程发起调用后,会阻塞等待C++函数执行完毕并返回结果。这适用于那些执行速度快、确定性高的操作,比如一个简单的数学计算、从本地缓存中读取一个配置值。
- 优点:编程模型简单直观,和调用普通JS函数一样。
- 风险:如果C++函数执行耗时较长(比如超过16ms),会阻塞JS主线程,导致UI卡顿甚至应用无响应(ANR)。这是新手最容易踩的坑。
异步调用:JS线程发起调用后立即返回,C++函数在后台线程(或自己创建的线程)中执行,执行完毕后通过回调函数(Callback)或Promise将结果传回JS。这适用于文件IO、网络请求、复杂算法等耗时操作。
- 优点:不阻塞主线程,应用响应流畅。
- 复杂度:需要处理回调、上下文保持、线程安全等问题,实现起来更复杂。
选择建议:除非你能百分百确定C++函数执行时间极短(微秒级),否则一律优先考虑异步模式。鸿蒙的UI框架是单线程模型,保持主线程畅通至关重要。在后续的示例中,我会重点展示异步调用的实现。
2.3 项目结构规划
一个清晰的目录结构能让后续开发少很多麻烦。假设你的工程名叫JsCppDemo,推荐如下结构:
JsCppDemo/ ├── entry/ # 主模块 │ ├── src/ │ │ ├── main/ │ │ │ ├── ets/ # ArkTS/JS业务代码 │ │ │ │ ├── pages/ │ │ │ │ └── utils/ │ │ │ │ └── NativeModule.ets # 封装Native调用的TS类 │ │ │ ├── cpp/ # C++ Native代码 │ │ │ │ ├── CMakeLists.txt # C++编译脚本 │ │ │ │ ├── native_module.cpp # Native实现 │ │ │ │ └── native_module.h # 头文件 │ │ │ └── resources/ │ └── oh-package.json5 └── oh_modules/ # 依赖关键点在于entry/src/main/cpp这个目录,它是放置所有C/C++源码和编译配置的地方。CMakeLists.txt是告诉鸿蒙编译工具链如何编译你的C++代码的核心文件。
3. 环境准备与工具链配置
3.1 开发环境清单
- IDE:DevEco Studio 4.0或更高版本,并确认已安装
Native开发套件。 - SDK:HarmonyOS SDK中必须包含
Native组件。在DevEco Studio的Settings > SDK Manager中检查并安装。 - 工具链:主要是CMake和Ninja。DevEco Studio通常会帮你配置好,但最好在终端输入
cmake --version和ninja --version确认一下。网络上很多C++环境问题,比如error: microsoft visual c++ 14.0 or greater is required,那是Windows上编译Python包时的常见错误,和鸿蒙Native开发无关。鸿蒙的工具链是基于LLVM/Clang的。
3.2 创建支持Native能力的工程
- 打开DevEco Studio,新建一个
Empty Ability工程。 - 在
Project视图的entry目录上右键,选择New > Native C++。这个操作至关重要,它会自动完成几件事:- 在
src/main下创建cpp目录及示例文件。 - 在
entry的build-profile.json5文件中添加"externalNativeOptions"配置块。 - 在
cpp目录下生成一个基础的CMakeLists.txt。
- 在
- 检查
entry目录下的build-profile.json5,应该能看到类似下面的配置,如果没有,可能需要手动添加:
"buildOption": { "externalNativeOptions": { "path": "./src/main/cpp/CMakeLists.txt", // CMake脚本路径 "arguments": "", "cppFlags": "" } }注意:很多初次尝试的开发者会直接手动创建
cpp文件夹和文件,但忘了在build-profile.json5里配置externalNativeOptions,导致C++代码根本不会被编译。所以,强烈建议使用IDE的New > Native C++菜单来初始化。
3.3 CMakeLists.txt基础配置解析
自动生成的CMakeLists.txt内容比较基础。一个功能更完善的配置示例如下:
# 设置CMake最低版本和项目名 cmake_minimum_required(VERSION 3.4.1) project(JsCppDemo) # 项目名,可自定义 # 设置C++标准 set(CMAKE_CXX_STANDARD 11) # 根据需求选择11、14、17等 set(CMAKE_CXX_STANDARD_REQUIRED ON) # 添加宏定义,通常用于区分调试和发布版本 add_definitions(-DDEBUG) # 示例,实际中可能通过其他方式传递 # 查找并链接鸿蒙NDK提供的必要库 # z是压缩库,hilog是日志库,几乎必备 find_library(z-lib z) find_library(hilog-lib hilog) # 设置头文件搜索路径 # 这里添加当前目录和可能存在的第三方库头文件路径 include_directories(${CMAKE_CURRENT_SOURCE_DIR}) # 添加你的源文件,编译成动态库 add_library(native_module SHARED native_module.cpp) # native_module是库名 # 将你的库与系统库链接起来 target_link_libraries(native_module PUBLIC ${z-lib} ${hilog-lib})关键解释:
project(JsCppDemo):这个名字主要用于日志TAG,和最终的.so文件名无关。add_library(native_module SHARED ...):这行决定了输出的Native库文件名。在鸿蒙中,最终生成的库文件会被重命名为libnative_module.z.so。你的JS代码加载的就是这个名字(不带lib前缀和.z.so后缀,即native_module)。target_link_libraries:链接hilog库是为了使用OH_LOG_DEBUG等宏打印日志到控制台,这是调试Native代码的生命线。
4. 从C++到JS:接口定义与实现
4.1 理解napi接口规范
JSVM-API的实现基于Node-API(一个独立的API标准,原名N-API)。在鸿蒙的头文件中,它通常被包含为napi/napi.h。所有与JS交互的函数,其签名都有特定格式。
一个最基础的Native函数原型如下:
// native_module.h #ifndef NATIVE_MODULE_H #define NATIVE_MODULE_H #include <napi/napi.h> namespace { // 函数声明:所有的napi函数都有固定的参数 (napi_env, napi_callback_info) napi_value Add(napi_env env, napi_callback_info info); napi_value ProcessDataAsync(napi_env env, napi_callback_info info); // 模块初始化函数声明 napi_value Init(napi_env env, napi_value exports); } #endif4.2 实现一个同步函数:两数相加
让我们实现一个简单的同步函数Add,在C++中完成加法并返回给JS。
// native_module.cpp #include "native_module.h" #include <hilog/log.h> // 引入日志头文件 // 定义日志标签,方便过滤 static constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LOG_CORE, LOG_DOMAIN, "JsCppDemo"}; // 同步函数 Add 的实现 napi_value Add(napi_env env, napi_callback_info info) { // 1. 获取参数个数和参数数组 size_t argc = 2; napi_value args[2] = {nullptr}; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); // 2. 参数校验(非常重要!) if (argc < 2) { napi_throw_error(env, nullptr, "Wrong number of arguments. Expect 2 numbers."); return nullptr; // 抛出异常后返回nullptr } napi_valuetype valuetype0, valuetype1; napi_typeof(env, args[0], &valuetype0); napi_typeof(env, args[1], &valuetype1); if (valuetype0 != napi_number || valuetype1 != napi_number) { napi_throw_type_error(env, nullptr, "Both arguments must be numbers."); return nullptr; } // 3. 从napi_value中提取C++数值 double value0, value1; napi_get_value_double(env, args[0], &value0); napi_get_value_double(env, args[1], &value1); // 4. 执行核心计算逻辑 double result = value0 + value1; // 5. 打印日志到控制台(调试用) OH_LOG_INFO(LABEL, "Add called: %{public}f + %{public}f = %{public}f", value0, value1, result); // 6. 将C++结果转换回napi_value并返回 napi_value sum; napi_create_double(env, result, &sum); return sum; }实操心得:
- 参数校验是必须的:JS是弱类型语言,传任何东西都有可能。如果不校验,直接从
args里提取数值,一旦传入非数字类型,会导致应用崩溃(Native Crash),错误信息很难定位。所以,napi_typeof和错误抛出是健壮代码的标配。 - 善用
hilog日志:OH_LOG_INFO、OH_LOG_ERROR是你调试Native代码的眼睛。在DevEco Studio的Log窗口,可以通过标签JsCppDemo过滤出你的日志。打印关键参数、函数入口和出口,能极大提升排查效率。
4.3 实现一个异步函数:模拟耗时计算
同步函数简单,但实际开发中异步才是主流。下面实现一个ProcessDataAsync,它接受一个数组和回调函数,在后台线程处理完后通过回调通知JS。
// 异步工作上下文结构体,用于在线程间传递数据 struct AsyncWorkContext { napi_env env = nullptr; napi_ref callbackRef = nullptr; // 用于保存JS回调函数的引用 std::vector<double> inputData; double processedResult = 0.0; napi_deferred deferred = nullptr; // 如果使用Promise,则需要这个 napi_async_work work = nullptr; // 异步工作对象 }; // 异步工作线程中执行的函数(在后台线程运行) void ExecuteWork(napi_env env, void* data) { auto* context = static_cast<AsyncWorkContext*>(data); // 模拟耗时计算,比如一个累加或复杂算法 double sum = 0.0; for (const auto& num : context->inputData) { sum += num; // 模拟计算耗时 usleep(10000); // 休眠10毫秒 } context->processedResult = sum; OH_LOG_INFO(LABEL, "Async work executed, result: %{public}f", sum); } // 异步工作完成后的回调(回到JS线程运行) void CompleteWork(napi_env env, napi_status status, void* data) { auto* context = static_cast<AsyncWorkContext*>(data); // 准备回调函数的参数 napi_value argv[2]; napi_get_null(env, &argv[0]); // 第一个参数是error,null表示无错误 napi_create_double(env, context->processedResult, &argv[1]); // 第二个参数是结果 // 获取保存的JS回调函数 napi_value callback; napi_get_reference_value(env, context->callbackRef, &callback); // 调用JS回调函数 napi_value global; napi_get_global(env, &global); napi_value result; napi_call_function(env, global, callback, 2, argv, &result); // 清理工作:删除回调函数引用、删除异步工作对象、释放上下文内存 napi_delete_reference(env, context->callbackRef); napi_delete_async_work(env, context->work); delete context; OH_LOG_INFO(LABEL, "Async work completed and callback invoked."); } // 暴露给JS的异步函数 napi_value ProcessDataAsync(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); // 参数校验:第一个是数组,第二个是函数 napi_valuetype valuetype0, valuetype1; napi_typeof(env, args[0], &valuetype0); napi_typeof(env, args[1], &valuetype1); if (valuetype0 != napi_object || valuetype1 != napi_function) { napi_throw_error(env, nullptr, "Arguments: (Array, Function) expected."); return nullptr; } // 1. 从JS数组提取数据到C++ vector std::vector<double> inputVec; uint32_t arrayLength; napi_get_array_length(env, args[0], &arrayLength); inputVec.reserve(arrayLength); for (uint32_t i = 0; i < arrayLength; ++i) { napi_value element; napi_get_element(env, args[0], i, &element); double value; napi_get_value_double(env, element, &value); inputVec.push_back(value); } // 2. 创建异步工作上下文,并保存数据 auto* context = new AsyncWorkContext(); context->env = env; context->inputData = std::move(inputVec); // 创建对JS回调函数的持久化引用,防止被垃圾回收 napi_create_reference(env, args[1], 1, &(context->callbackRef)); // 3. 创建异步工作对象 napi_value resourceName; napi_create_string_utf8(env, "AsyncDataProcessor", NAPI_AUTO_LENGTH, &resourceName); napi_create_async_work(env, nullptr, resourceName, ExecuteWork, // 后台执行函数 CompleteWork, // 完成回调函数 context, // 传递的上下文数据 &(context->work)); // 输出的async_work对象 // 4. 将异步工作队列到线程池中执行 napi_queue_async_work(env, context->work); // 5. 返回undefined给JS(因为结果是异步返回的) napi_value undefined; napi_get_undefined(env, &undefined); return undefined; }这是整个交互流程中最复杂也最核心的部分,有几个关键点必须理解:
- 两个线程:
ExecuteWork在系统管理的后台线程池中运行,这里可以执行任何耗时操作。CompleteWork在异步任务完成后,被调用回到创建napi_env的JS线程(通常是主线程)中执行,只有在这里才能安全地调用napi_call_function等会操作JS对象的接口。 - 上下文
Context:由于两个函数在不同的线程执行,必须通过一个自定义的结构体(AsyncWorkContext)来传递数据。这个结构体需要手动管理内存(new/delete)。 - 引用
napi_ref:JS函数(回调函数)是一个JS对象,不能直接保存在C++结构体中跨线程使用。需要用napi_create_reference创建一个“强引用”,防止它被垃圾回收,并在完成后用napi_delete_reference释放。 - 错误处理:异步流程中的错误处理更复杂。如果
ExecuteWork中发生异常,通常需要将错误信息传递到CompleteWork,然后以第一个参数的形式传递给JS回调函数。
4.4 模块的初始化与导出
最后,我们需要一个入口函数来声明这个Native模块对外暴露了哪些方法。
// native_module.cpp (续) // 模块初始化函数 napi_value Init(napi_env env, napi_value exports) { // 定义要导出的属性描述符 napi_property_descriptor desc[] = { {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr}, {"processDataAsync", nullptr, ProcessDataAsync, nullptr, nullptr, nullptr, napi_default, nullptr} }; // 将属性定义到exports对象上 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); OH_LOG_INFO(LABEL, "Native module initialized successfully."); return exports; } // 注册模块的宏定义 NAPI_MODULE(native_module, Init) // 这里的`native_module`必须和CMakeLists.txt中add_library的库名一致NAPI_MODULE宏是模块的注册点。第一个参数native_module是模块名,JS侧加载时使用的就是这个名字。第二个参数Init是初始化函数指针。
5. JS/ArkTS侧的调用封装
Native层准备好了,现在需要在JS侧进行加载和调用。
5.1 使用napi接口加载Native模块
在ArkTS/JS中,你需要使用@ohos.napi接口来加载编译好的Native库。
// entry/src/main/ets/utils/NativeModule.ets import napi from '@ohos.napi'; export class NativeModule { // 加载Native库。'native_module' 对应C++编译出的库名 private nativeModule: napi.NAPIModule = napi.loadModule('native_module'); // 封装同步调用 add(a: number, b: number): number { try { // 调用Native方法。方法名必须和C++中导出的完全一致。 return this.nativeModule.add(a, b) as number; } catch (error) { console.error(`[NativeModule] add failed: ${JSON.stringify(error)}`); return NaN; } } // 封装异步调用(回调方式) processDataAsync(data: number[], callback: (error: Error | null, result?: number) => void): void { try { this.nativeModule.processDataAsync(data, callback); } catch (error) { console.error(`[NativeModule] processDataAsync failed: ${JSON.stringify(error)}`); callback(new Error(`Native call error: ${error}`)); } } // 封装异步调用(Promise方式)- 更现代的写法 processDataAsyncPromise(data: number[]): Promise<number> { return new Promise((resolve, reject) => { this.processDataAsync(data, (error, result) => { if (error) { reject(error); } else { resolve(result!); } }); }); } }5.2 在UI页面中调用
创建一个简单的UI页面来测试我们的Native模块。
// entry/src/main/ets/pages/Index.ets import { NativeModule } from '../utils/NativeModule'; @Entry @Component struct Index { private nativeModule: NativeModule = new NativeModule(); @State sum: number = 0; @State asyncResult: string = 'Waiting...'; build() { Column({ space: 20 }) { Text('JSVM-API Demo').fontSize(30).fontWeight(FontWeight.Bold) Button('Test Sync Add: 5 + 3') .onClick(() => { this.sum = this.nativeModule.add(5, 3); }) Text(`Sync Result: ${this.sum}`).fontSize(20) Divider().height(10) Button('Test Async Process (Callback)') .onClick(() => { this.asyncResult = 'Processing...'; let testData = [1.1, 2.2, 3.3, 4.4, 5.5]; this.nativeModule.processDataAsync(testData, (error, result) => { if (error) { this.asyncResult = `Error: ${error.message}`; } else { this.asyncResult = `Async Result: ${result?.toFixed(2)}`; } }); }) Button('Test Async Process (Promise)') .onClick(async () => { this.asyncResult = 'Processing...'; try { let testData = [10, 20, 30]; let result = await this.nativeModule.processDataAsyncPromise(testData); this.asyncResult = `Promise Result: ${result}`; } catch (error) { this.asyncResult = `Promise Error: ${error.message}`; } }) Text(this.asyncResult).fontSize(18).fontColor(Color.Blue) } .width('100%') .height('100%') .padding(20) .justifyContent(FlexAlign.Center) } }6. 编译、运行与调试
6.1 编译流程与产物
- 点击DevEco Studio的
Build > Build Haps(s)。 - 编译过程是两阶段的:
- 第一阶段(JS/ETS):将ArkTS/JS代码编译打包。
- 第二阶段(Native):CMake被调用,编译
cpp目录下的源代码,生成libnative_module.z.so文件。这个文件会被自动打包到最终的.hap文件中。
- 你可以在
entry/build/default/intermediates/libs/default/arm64-v8a/(根据你的目标架构)目录下找到生成的.so文件,确认其是否存在。
6.2 真机运行与日志查看
- 将应用运行到鸿蒙Next真机或模拟器上。
- 打开DevEco Studio的
Log窗口,选择你的设备进程。 - 在过滤器中输入你设置的日志标签,例如
JsCppDemo,就能看到来自C++层的OH_LOG_INFO日志了。这是验证Native代码是否执行、参数是否正确的最直接证据。
6.3 常见编译与运行问题排查
问题1:编译错误undefined reference to 'napi_xxx'
- 原因:没有正确链接Node-API库。虽然头文件包含了,但链接器找不到实现。
- 解决:确保你的
CMakeLists.txt中通过find_library找到了正确的NDK库路径,并且target_link_libraries中链接了必要的库(如libace_napi.z.so的抽象层)。对于纯napi函数,通常NDK已默认链接,此错误可能表明CMake配置有误或SDK不完整。检查SDK中Native组件的安装。
问题2:运行时崩溃java.lang.UnsatisfiedLinkError
- 原因:这是最常见的问题。JS层找不到对应的Native库或库中的符号(函数)。
- 排查步骤:
- 库名不匹配:检查
NAPIModule.loadModule('xxx')中的'xxx'是否与CMakeLists.txt中add_library(xxx SHARED ...)以及NAPI_MODULE(xxx, Init)中的名字完全一致。大小写敏感。 - 函数名不匹配:检查JS调用的函数名(如
add)是否与C++中napi_property_descriptor里定义的以及Init函数中导出的名字完全一致。 - ABI不匹配:你的hap包是否包含了当前设备CPU架构(如arm64-v8a)对应的
.so文件?检查build-profile.json5中的"abiFilters"是否包含了"arm64-v8a"。 - Native Crash:如果日志中出现了
signal、SIGSEGV等,则是C++代码本身有内存错误(空指针、数组越界、类型转换错误)。仔细检查你的C++逻辑,特别是数组操作和指针。使用hilog在关键步骤打印信息来定位。
- 库名不匹配:检查
问题3:异步回调没有被执行
- 原因:
napi_create_async_work或napi_queue_async_work调用失败。AsyncWorkContext在CompleteWork被调用前就被意外释放了。ExecuteWork函数中发生未捕获的异常,导致工作线程提前终止。
- 解决:
- 检查每个
napi_系列函数的返回值(napi_status),虽然示例中省略了,但生产代码应该检查并处理错误。 - 确保
context对象是用new在堆上分配的,并且只在CompleteWork的最后用delete释放。 - 在
ExecuteWork内部做好异常捕获,将错误信息设置到context中,在CompleteWork里传递给JS。
- 检查每个
问题4:性能问题
- 场景:频繁进行JS-Native互操作,发现性能开销很大。
- 分析:每次跨语言调用都有序列化/反序列化的开销。对于需要批量处理的数据(比如一个大型数组),应尽量减少调用次数。
- 优化:不要逐个元素地在JS和C++间传递。像上面的异步示例一样,在单次调用中传递整个数组(或ArrayBuffer)。对于更复杂的数据结构,考虑使用
napi_create_arraybuffer和napi_get_arraybuffer_info来共享内存,避免拷贝。
7. 进阶话题与最佳实践
7.1 复杂数据类型的传递
除了数字、字符串、布尔值和数组,你还需要传递对象、函数等。
- 传递对象:使用
napi_get_property_names和napi_get_named_property来遍历和获取对象属性。 - 传递ArrayBuffer(共享内存):对于大量二进制数据(如图像像素数据),使用ArrayBuffer可以极大提升性能。在C++侧通过
napi_get_arraybuffer_info获取数据指针和长度,直接操作内存。 - 从C++创建JS对象/数组:使用
napi_create_object、napi_create_array_with_length、napi_set_element、napi_set_named_property等函数,可以在C++侧构造复杂的JS对象返回给前端。
7.2 线程安全与内存管理
- napi_env不是线程安全的:
napi_env对象不能跨线程直接使用。ExecuteWork中传入的env是受限的,只能调用有限的线程安全API(如napi_create_buffer)。与JS对象交互的操作必须在CompleteWork或JS线程中进行。 - 避免内存泄漏:所有通过
napi_create_reference创建的引用,都必须有对应的napi_delete_reference。所有通过napi_create_async_work创建的工作对象,也必须有对应的napi_delete_async_work(通常在CompleteWork中清理)。 - 使用
napi_create_threadsafe_function:对于需要从任意C++线程主动向JS线程发送消息的场景(如事件通知),这是更高级和安全的机制。
7.3 将C++第三方库集成进来
假设你有一个用C++写的、计算KMP算法next数组的库,你想在鸿蒙应用中使用它。
- 源码集成:将第三方库的源码(.cpp/.h)放入你的
cpp目录,并在CMakeLists.txt的add_library中将它们加入源文件列表。 - 预编译库集成:如果第三方库提供了预编译的
.a或.so文件。- 将库文件放入
cpp/libs/目录(需自己创建)。 - 在
CMakeLists.txt中,使用target_link_libraries(your_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libs/third_party.a)来链接静态库。对于动态库,处理方式更复杂,可能需要一起打包。 - 注意鸿蒙的NDK环境:第三方库必须使用鸿蒙的NDK(或兼容的LLVM工具链)编译,直接使用为Android或Linux编译的库很可能不兼容。
- 将库文件放入
7.4 关于“Legacy JS API”的警告
在开发过程中,你可能会在日志或文档中看到类似deprecation warning [legacy-js-api]: the legacy js api is deprecated的警告。这通常指的是更早版本的、非napi标准的JS绑定方式(如jerryscript相关API)。我们现在使用的napi接口是当前鸿蒙推荐的标准方式,不受此警告影响。确保你#include <napi/napi.h>并使用本文所述的napi_前缀函数即可。
整个流程走下来,你会发现JSVM-API就像是在JS的灵动世界和C++的性能王国之间修建了一条双向高速公路。初期搭建桥墩(环境配置、基础封装)会有些繁琐,但一旦通车,你的应用能力边界将得到巨大扩展。关键在于理解线程模型、内存管理和错误处理的“交通规则”。多写日志,从小功能开始验证,逐步构建复杂的交互逻辑,你就能越来越熟练地驾驭这条高速通道,开发出真正强大的鸿蒙Next应用。