
个人首页: VON
鸿蒙系列专栏: 鸿蒙开发小型案例总结
综合案例 :鸿蒙综合案例开发
鸿蒙6.0:从0开始的开源鸿蒙6.0.0
鸿蒙5.0:鸿蒙5.0零基础入门到项目实战
从概念到踩坑的跨语言开发入门

前言
在鸿蒙(HarmonyOS)应用开发中,ArkTS/JS 以高效便捷占据主流,但面对音视频编解码、高性能计算等场景时,编译型语言 C/C++ 的性能优势不可替代。此时,NAPI(Native API) 便成为连接 ArkTS 与原生世界的桥梁 —— 它是鸿蒙生态中实现跨语言交互的标准化框架,让两种语言既能各司其职,又能无缝协作。
一、NAPI 是什么:跨语言交互的 “翻译官”
NAPI 并非鸿蒙独创,其设计参考了 Node.js 的 N-API 规范,本质是一套稳定的二进制接口(ABI)。它的核心角色是 “翻译官”:
让 ArkTS 代码能像调用自身函数一样调用 C/C++ 原生方法;
让原生代码能反向触发 ArkTS 的回调逻辑;
屏蔽不同语言的底层差异(如数据类型、内存管理),实现 “一次开发,多端适配”。
在鸿蒙 6.0(API20)中,NAPI 的价值尤为突出:既保留 ArkTS 的开发效率,又能复用 C/C++ 原生库(如 OpenCV、FFmpeg),同时适配手机、车机等全场景设备。
二、NAPI 的核心原理:三大支柱支撑跨语言通信
NAPI 的跨语言交互并非 “黑盒”,其底层围绕上下文管理、数据转换、函数调用三大支柱展开:
上下文环境:Env 的 “地基” 作用
任何 NAPI 调用都依赖Env(环境对象),它是跨语言通信的 “全局状态管理器”:
存储当前调用的上下文(如 ArkTS 引擎状态、内存记录);
提供内存管理接口(原生代码创建的对象需依附于 Env 的生命周期);
保证线程安全(Env 与线程一一绑定,不可跨线程共享)。
简单来说,Env 是 NAPI 的 “操作地基”,所有交互都必须在 Env 的框架内进行。
数据类型映射:打通 “数据壁垒”
ArkTS 与 C/C++ 的类型体系差异巨大(如 ArkTS 的Number对应 C++ 的int/double),NAPI 通过统一容器 + 类型转换接口解决这一问题:
统一容器:用napi_value封装所有 ArkTS 数据(数字、字符串、函数等在原生代码中均以napi_value表示);
类型转换:通过napi_get_value_double(ArkTS Number 转 C++ double)、napi_create_string_utf8(C++ 字符串转 ArkTS String)等接口,实现数据的双向映射。
例如,将 ArkTS 传入的两个数字相加,原生代码需先通过napi_get_value_double提取数值,计算后再通过napi_create_double返回结果。
函数调用:双向通信的实现逻辑
NAPI 支持两种核心调用模式,覆盖不同业务场景:
- ArkTS 调用原生函数:需先通过NAPI_MODULE宏注册原生模块,将原生函数与 ArkTS 可访问的方法名绑定;ArkTS 导入模块后,引擎会自动完成参数转换与函数执行。
- 原生调用 ArkTS 回调:ArkTS 将回调函数作为参数传入原生模块,原生代码在异步任务完成后(如 IO 操作),通过napi_call_function触发回调,将结果返回给 ArkTS。
创建Native项目

一定要选择这个

然后点击创建即可

三、从实践踩坑看 NAPI 开发的关键注意事项
理论看似简单,但实际开发中容易踩中 “名称不匹配”“配置错误” 等坑 —— 以下是基于真实开发的避坑指南:
3.1、模块名必须 “全局统一”
NAPI 开发的核心原则是 “一处命名,处处统一”,否则会出现 “模块找不到” 的问题:
CMake 的project名、add_library名需与原生代码的nm_modname一致;
ArkTS 导入的库名需与 CMake 生成的动态库名一致(如 CMake 生成libentry.so,ArkTS 需导入’libentry.so’);
types目录下的声明文件需与模块名对应(如types/libentry对应模块entry)。
踩坑案例:若 CMake 写project(demoModule),但原生代码nm_modname = “entry”,会导致 ArkTS 导入时找不到模块。
这里的名字一点要保持一致

我这里对原生的cmake文件做了一个简单的修改大家可以直接用我的cmake
cmake_minimum_required(VERSION 3.14)
if(DEFINED CMAKE_TOOLCHAIN_FILE)
include(${CMAKE_TOOLCHAIN_FILE})
endif()
project(entry)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 新增:添加鸿蒙SDK头文件路径
include_directories(
"${CMAKE_TOOLCHAIN_FILE}/../../include"
"${OHOS_SDK_NATIVE}/include"
"${HMOS_SDK_NATIVE}/include"
)
link_directories(
"${CMAKE_TOOLCHAIN_FILE}/../../libs/${CMAKE_OHOS_ARCH_ABI}"
"${OHOS_SDK_NATIVE}/libs/${CMAKE_OHOS_ARCH_ABI}"
)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
代码解释
这段代码是鸿蒙 6.0(API20)环境下 NAPI 原生模块的 CMake 构建配置文件,负责指导编译器完成原生代码的编译、链接,最终生成可被 ArkTS 调用的动态库。
指定 CMake 最低版本
cmake_minimum_required(VERSION 3.14)
要求当前环境的 CMake 版本不低于 3.14(鸿蒙 6.0 推荐的 CMake 版本,确保兼容新特性)。
导入鸿蒙工具链(适配鸿蒙环境)
if(DEFINED CMAKE_TOOLCHAIN_FILE)
include(${CMAKE_TOOLCHAIN_FILE})
endif()
CMAKE_TOOLCHAIN_FILE是 DevEco Studio 自动传入的鸿蒙原生工具链文件路径(包含编译规则、编译器路径等);
通过include导入该文件,让 CMake 遵循鸿蒙的编译标准(如适配鸿蒙的系统架构、编译器选项)。
配置项目与编译标准
project(entry)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(entry):定义项目名称为entry(需与 NAPI 模块名、动态库名一致);
set(CMAKE_CXX_STANDARD 17):指定 C++ 编译标准为 C++17(鸿蒙 NAPI 推荐的标准,兼容现代语法);
CMAKE_CXX_STANDARD_REQUIRED ON:强制使用指定的 C++ 标准,若编译器不支持则报错。
添加头文件路径(解决头文件找不到的问题)
include_directories(
"${CMAKE_TOOLCHAIN_FILE}/../../include"
"${OHOS_SDK_NATIVE}/include"
"${HMOS_SDK_NATIVE}/include"
)
include_directories:告诉编译器头文件的搜索目录;
路径是从鸿蒙 SDK 中提取的原生头文件目录(如napi/native_api.h、ohos/log.h都存放在这些目录下),避免出现 “头文件找不到” 的编译错误。
添加库文件路径(链接依赖库)
link_directories(
"${CMAKE_TOOLCHAIN_FILE}/../../libs/${CMAKE_OHOS_ARCH_ABI}"
"${OHOS_SDK_NATIVE}/libs/${CMAKE_OHOS_ARCH_ABI}"
)
link_directories:告诉链接器依赖库的搜索目录;
${CMAKE_OHOS_ARCH_ABI}是自动变量(如arm64-v8a、x86_64),确保链接对应架构的库文件(如 NAPI 核心库libace_napi.z.so)。
编译动态库(生成 NAPI 模块)
add_library(entry SHARED napi_init.cpp)
add_library(entry SHARED …):编译生成名为entry的动态库(SHARED表示动态库,鸿蒙 NAPI 必须用动态库);
napi_init.cpp是原生代码文件(包含 NAPI 函数、模块注册逻辑),可添加多个源文件(如napi_init.cpp utils.cpp)。
链接 NAPI 核心库
target_link_libraries(entry PUBLIC libace_napi.z.so)
target_link_libraries:将当前动态库与鸿蒙 NAPI 核心库libace_napi.z.so链接;
libace_napi.z.so是鸿蒙 6.0 的 NAPI 依赖库(旧版本是libnapi.so),提供napi_get_value_double等 NAPI 接口的实现,是 NAPI 开发的必备依赖。
核心作用总结
这段 CMake 配置的最终目标是:将napi_init.cpp编译为libentry.so动态库,并确保其能正确调用鸿蒙 NAPI 的接口,最终被 ArkTS 代码导入并调用。
动态库会被编译在这里

3.2、鸿蒙 6.0 的配置适配:别用旧接口
我这里在日志部分卡了好久,为了测试是否调用成功来输出日志进行检测。
// 正确导入hilog
import hilog from '@ohos.hilog';
// 导入原生模块
import testNapi from 'libentry.so';
// 自定义日志域
const DOMAIN = 0x1234;
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.onClick(() => {
hilog.info(DOMAIN, 'testTag', 'NAPI调用结果:%{public}d', testNapi.add(2,3));
this.message = 'Welcome';
})
}
.width('100%')
}
.height('100%')
}
}
这段代码是鸿蒙 ArkTS 页面组件,核心功能是通过 NAPI 调用 C/C++ 原生方法,并结合日志打印交互结果,是典型的 “ArkTS + NAPI” 跨语言协作示例。
代码解释
这里其实主要是通过 NAPI 调用 C/C++ 原生方法,对于有基础的朋友们肯定是可以看懂的,我这里为了大多数人还是简单解释下吧
依赖导入
// 正确导入hilog:鸿蒙系统日志工具,用于打印调试/业务日志
import hilog from '@ohos.hilog';
// 导入原生模块:加载C/C++编译生成的动态库libentry.so,testNapi对应原生模块的方法集合
import testNapi from 'libentry.so';
@ohos.hilog是鸿蒙官方日志工具,替代了旧版本的@kit.HilogKit;
libentry.so是之前通过 CMake 编译生成的 NAPI 动态库,testNapi是该库暴露给 ArkTS 的方法对象(如add方法)。
日志域定义
// 自定义日志域:用于区分不同模块的日志(避免与系统日志冲突)
const DOMAIN = 0x1234;
DOMAIN是日志的 “分类标识”(通常用十六进制数),可自定义(需避免使用系统保留的0x0000);
打印日志时需指定该域,方便在 Logcat 中筛选特定模块的日志。
组件与状态定义
@Entry // 标识该组件是应用的入口页面
@Component // 标识这是一个ArkTS自定义组件
struct Index {
@State message: string = 'Hello World'; // 响应式状态:值变化时会自动更新UI
@Entry+@Component是鸿蒙页面组件的基础注解;
@State是响应式状态装饰器:当message的值改变时,依赖它的 UI(如Text组件)会自动重新渲染。
UI 布局与交互逻辑
build() { // 组件的UI构建方法,定义页面结构
Row() { // 横向容器(类似HTML的div)
Column() { // 纵向容器
Text(this.message) // 文本组件,显示message的内容
.fontSize($r('app.float.page_text_font_size')) // 字体大小(从应用资源文件中读取,支持多端适配)
.fontWeight(FontWeight.Bold) // 字体加粗
.onClick(() => { // 点击事件回调:用户点击Text时执行
// 调用原生模块的add方法,并打印结果(%{public}d是整数占位符)
hilog.info(DOMAIN, 'testTag', 'NAPI调用结果:%{public}d', testNapi.add(2,3));
this.message = 'Welcome'; // 修改响应式状态,触发UI更新
})
}
.width('100%') // 纵向容器占满父容器宽度
}
.height('100%') // 横向容器占满页面高度
}
布局采用Row+Column的嵌套结构,是鸿蒙 UI 的基础布局方式;
onClick是核心交互逻辑:
- 调用原生模块testNapi的add方法(传入 2 和 3,执行 C/C++ 的加法逻辑);
- 通过hilog.info打印调用结果(%{public}d是整数占位符,匹配add返回的数值);
- 修改message的值为Welcome,触发Text组件的 UI 更新。
核心逻辑总结
这段代码实现了 “ArkTS 触发点击事件 → 调用 C/C++ 原生方法 → 打印结果并更新 UI” 的完整流程,是鸿蒙 NAPI 开发的典型场景:
- 前端(ArkTS)负责 UI 交互与日志展示;
- 后端(C/C++)负责高性能计算(这里是加法示例,实际可扩展为复杂逻辑);
- NAPI 作为桥梁,让两者无缝协作。
3.3、日志输出:占位符与筛选的细节
日志是调试 NAPI 的关键,但容易因 “格式错误”“筛选配置” 导致无输出:
- 占位符格式:鸿蒙hilog的公共占位符是%{public}(无需额外类型标识,如%{public}f是错误写法);
- 筛选配置:需将 Logcat 级别设为 “Debug”,并选择当前应用的 “所有日志”,避免遗漏自定义日志。
- 踩坑案例:写%{public}f会导致日志显示异常(如NAPI调用结果:}f),改为%{public}即可正常显示。

3.4、指定原生代码编译的目标 CPU 架构
之前尝试QT的时候模拟器打不开好像就是这个问题,需要在配置文件中进行修改,只需要在这里添加一行即可

什么是 “ABI”?
ABI(Application Binary Interface)是 “应用二进制接口”,决定了原生代码(C/C++)编译后的二进制文件能在哪些 CPU 架构的设备上运行。常见的鸿蒙设备架构有:
- arm64-v8a:主流手机、平板等移动设备的 64 位架构;
- x86_64:模拟器、部分 PC 设备的 64 位架构。
为什么要添加abiFilters?
适配目标设备:鸿蒙应用需要运行在不同架构的设备上(如真机是arm64-v8a,模拟器是x86_64),指定abiFilters后,CMake 会为这些架构分别编译对应的动态库(libentry.so),保证应用在对应设备上能加载原生模块。
- 减少包体积:若不指定,CMake 可能会编译所有支持的架构(如armeabi-v7a、arm64-v8a、x86_64等),导致 HAP 包体积增大;通过abiFilters可只编译需要的架构,精简包体积。
- 避免架构不匹配错误:若应用只编译了arm64-v8a的动态库,却运行在x86_64的模拟器上,会出现 “无法找到原生库” 的崩溃;指定多架构可避免这类兼容性问题。
这段配置的实际效果
添加"abiFilters": [“arm64-v8a”, “x86_64”]后:
- CMake 会分别为arm64-v8a和x86_64架构编译libentry.so;
- 编译产物会分别存放在build/default/intermediates/cmake/default/obj/arm64-v8a和x86_64目录下;
- 打包时,这些架构的动态库会被一起打入 HAP 包,应用运行时会自动加载对应架构的库。

四、总结:NAPI 是鸿蒙开发的 “能力扩展器”
NAPI 并非替代 ArkTS,而是作为其能力补充 —— 它让鸿蒙应用既能享受 ArkTS 的高效开发,又能借助 C/C++ 的性能优势覆盖复杂场景。从概念到实践,NAPI 的核心是 “标准化”:统一的接口、统一的命名、统一的适配规则,让跨语言开发不再是 “黑盒”。
对于鸿蒙开发者而言,掌握 NAPI 不仅是学会一项技术,更是打开了高性能、全场景应用开发的大门 —— 而避开 “名称不匹配”“接口升级” 等坑,正是从 “初识” 到 “熟练” 的关键一步。
浙公网安备 33010602011771号