news 2026/7/20 10:09:35

鸿蒙Next JSVM-API开发指南:JS与C++跨语言交互实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙Next JSVM-API开发指南:JS与C++跨语言交互实战

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的doubleint32_t)、内存管理(谁申请,谁释放,如何避免泄漏)、以及异常处理。

为什么是“JSVM”?这里的“VM”指的是管理JavaScript执行的虚拟机(在鸿蒙中是ARK引擎)。JSVM-API就是给这个虚拟机提供的、用于与外部Native世界通信的底层接口。所以,你的C++代码实际上是在和JS虚拟机打交道,而不是直接操作JS对象。

2.2 两种交互模式:同步与异步

在设计交互层时,首先要根据场景决定模式:

  1. 同步调用:JS线程发起调用后,会阻塞等待C++函数执行完毕并返回结果。这适用于那些执行速度快、确定性高的操作,比如一个简单的数学计算、从本地缓存中读取一个配置值。

    • 优点:编程模型简单直观,和调用普通JS函数一样。
    • 风险:如果C++函数执行耗时较长(比如超过16ms),会阻塞JS主线程,导致UI卡顿甚至应用无响应(ANR)。这是新手最容易踩的坑。
  2. 异步调用: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 --versionninja --version确认一下。网络上很多C++环境问题,比如error: microsoft visual c++ 14.0 or greater is required,那是Windows上编译Python包时的常见错误,和鸿蒙Native开发无关。鸿蒙的工具链是基于LLVM/Clang的。

3.2 创建支持Native能力的工程

  1. 打开DevEco Studio,新建一个Empty Ability工程。
  2. Project视图的entry目录上右键,选择New > Native C++。这个操作至关重要,它会自动完成几件事:
    • src/main下创建cpp目录及示例文件。
    • entrybuild-profile.json5文件中添加"externalNativeOptions"配置块。
    • cpp目录下生成一个基础的CMakeLists.txt
  3. 检查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); } #endif

4.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_INFOOH_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; }

这是整个交互流程中最复杂也最核心的部分,有几个关键点必须理解:

  1. 两个线程ExecuteWork在系统管理的后台线程池中运行,这里可以执行任何耗时操作。CompleteWork在异步任务完成后,被调用回到创建napi_env的JS线程(通常是主线程)中执行,只有在这里才能安全地调用napi_call_function等会操作JS对象的接口。
  2. 上下文Context:由于两个函数在不同的线程执行,必须通过一个自定义的结构体(AsyncWorkContext)来传递数据。这个结构体需要手动管理内存(new/delete)。
  3. 引用napi_ref:JS函数(回调函数)是一个JS对象,不能直接保存在C++结构体中跨线程使用。需要用napi_create_reference创建一个“强引用”,防止它被垃圾回收,并在完成后用napi_delete_reference释放。
  4. 错误处理:异步流程中的错误处理更复杂。如果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 编译流程与产物

  1. 点击DevEco Studio的Build > Build Haps(s)
  2. 编译过程是两阶段的:
    • 第一阶段(JS/ETS):将ArkTS/JS代码编译打包。
    • 第二阶段(Native):CMake被调用,编译cpp目录下的源代码,生成libnative_module.z.so文件。这个文件会被自动打包到最终的.hap文件中。
  3. 你可以在entry/build/default/intermediates/libs/default/arm64-v8a/(根据你的目标架构)目录下找到生成的.so文件,确认其是否存在。

6.2 真机运行与日志查看

  1. 将应用运行到鸿蒙Next真机或模拟器上。
  2. 打开DevEco Studio的Log窗口,选择你的设备进程。
  3. 在过滤器中输入你设置的日志标签,例如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库或库中的符号(函数)。
  • 排查步骤
    1. 库名不匹配:检查NAPIModule.loadModule('xxx')中的'xxx'是否与CMakeLists.txtadd_library(xxx SHARED ...)以及NAPI_MODULE(xxx, Init)中的名字完全一致。大小写敏感。
    2. 函数名不匹配:检查JS调用的函数名(如add)是否与C++中napi_property_descriptor里定义的以及Init函数中导出的名字完全一致。
    3. ABI不匹配:你的hap包是否包含了当前设备CPU架构(如arm64-v8a)对应的.so文件?检查build-profile.json5中的"abiFilters"是否包含了"arm64-v8a"
    4. Native Crash:如果日志中出现了signalSIGSEGV等,则是C++代码本身有内存错误(空指针、数组越界、类型转换错误)。仔细检查你的C++逻辑,特别是数组操作和指针。使用hilog在关键步骤打印信息来定位。

问题3:异步回调没有被执行

  • 原因
    1. napi_create_async_worknapi_queue_async_work调用失败。
    2. AsyncWorkContextCompleteWork被调用前就被意外释放了。
    3. ExecuteWork函数中发生未捕获的异常,导致工作线程提前终止。
  • 解决
    1. 检查每个napi_系列函数的返回值(napi_status),虽然示例中省略了,但生产代码应该检查并处理错误。
    2. 确保context对象是用new在堆上分配的,并且只在CompleteWork的最后用delete释放。
    3. ExecuteWork内部做好异常捕获,将错误信息设置到context中,在CompleteWork里传递给JS。

问题4:性能问题

  • 场景:频繁进行JS-Native互操作,发现性能开销很大。
  • 分析:每次跨语言调用都有序列化/反序列化的开销。对于需要批量处理的数据(比如一个大型数组),应尽量减少调用次数。
  • 优化:不要逐个元素地在JS和C++间传递。像上面的异步示例一样,在单次调用中传递整个数组(或ArrayBuffer)。对于更复杂的数据结构,考虑使用napi_create_arraybuffernapi_get_arraybuffer_info来共享内存,避免拷贝。

7. 进阶话题与最佳实践

7.1 复杂数据类型的传递

除了数字、字符串、布尔值和数组,你还需要传递对象、函数等。

  • 传递对象:使用napi_get_property_namesnapi_get_named_property来遍历和获取对象属性。
  • 传递ArrayBuffer(共享内存):对于大量二进制数据(如图像像素数据),使用ArrayBuffer可以极大提升性能。在C++侧通过napi_get_arraybuffer_info获取数据指针和长度,直接操作内存。
  • 从C++创建JS对象/数组:使用napi_create_objectnapi_create_array_with_lengthnapi_set_elementnapi_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数组的库,你想在鸿蒙应用中使用它。

  1. 源码集成:将第三方库的源码(.cpp/.h)放入你的cpp目录,并在CMakeLists.txtadd_library中将它们加入源文件列表。
  2. 预编译库集成:如果第三方库提供了预编译的.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应用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/20 10:07:30

世界模型技术:AI产业的新风口与落地实践

1. 世界模型技术爆发背后的产业逻辑2026年全球AI产业最引人注目的现象&#xff0c;莫过于世界模型领域95亿美元规模的投融资热潮。这个数字背后&#xff0c;反映的是大厂与初创企业在技术路线上的战略分野&#xff1a;科技巨头倾向于构建封闭的自研体系&#xff0c;而创业公司则…

作者头像 李华
网站建设 2026/7/20 10:06:53

C++图结构实现与算法详解:从邻接表到最短路径

1. 从“Hello World”到“图世界”&#xff1a;为什么C程序员绕不开图结构如果你刚开始学C&#xff0c;可能还在和指针、类、模板这些基础概念较劲。当你终于能写出一个像样的链表或二叉树时&#xff0c;可能会觉得数据结构的世界已经向你敞开了大门。但很快&#xff0c;无论是…

作者头像 李华
网站建设 2026/7/20 10:06:16

Mesop与Streamlit对比:Python Web框架选型指南

1. 初识Mesop&#xff1a;谷歌新秀框架的定位与特性Mesop是谷歌内部团队开发的一款开源Python框架&#xff0c;主要用于快速构建内部工具和原型开发。与Streamlit这类数据应用框架不同&#xff0c;Mesop更注重于提供高度可定制化的UI组件和交互体验。它的核心设计理念是"用…

作者头像 李华
网站建设 2026/7/20 10:06:08

重构游戏体验边界:Wand-Enhancer如何革新本地化增强生态

重构游戏体验边界&#xff1a;Wand-Enhancer如何革新本地化增强生态 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer 在数字游戏体验日益丰富的今天…

作者头像 李华
网站建设 2026/7/20 10:06:05

VS Code 集成 MiniMax M2 实现结构化代码补全

1. 项目概述&#xff1a;为什么在 VS Code 里直接调用 MiniMax M2 不是“锦上添花”&#xff0c;而是开发流的底层升级 我第一次在 VS Code 里把 MiniMax 的 M2 模型接入代码补全流程时&#xff0c;不是为了尝鲜&#xff0c;而是被现实逼出来的——团队新接手一个金融风控规则…

作者头像 李华