NDK(Native Development Kit) 是鸿蒙提供的原生开发工具集,允许开发者使用 C/C++ 编写底层代码,通过跨语言调用与 ArkTS 层交互。适用于性能敏感,复用C/C++库,底层硬件操作等场景。
创建 NDK 工程
可以直接使用 DevEco Studio 模板构建 NDK 工程
创建成功后,目录如下所示:
CMakeLists.txt 是鸿蒙原生 C++ 模块的构建配置文件,CMake 工具会根据它编译生成动态库(.so文件),供鸿蒙 ArkTS 层调用,我已经逐行解释含义了,不懂得直接看注释即可。
# 声明CMake所需的最低版本
cmake_minimum_required(VERSION 3.5.0)
# 定义项目名称
project(HarmonyApplication)
# 定义变量:CMAKE_CURRENT_SOURCE_DIR 为系统内置变量,代表当前 CMakeLists.txt 所在的文件夹路径
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 判断是否定义了 PACKAGE_FIND_FILE 变量,若是则引入该文件,鸿蒙自动生成的兼容配置,用于加载依赖包的配置,开发者无需手动修改
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif() # CMake 里 if 判断的结束标记,用来闭合 if 语句,CMake 不是 Java,没有大括号 {} 来圈定代码范围
# 添加头文件搜索路径:告诉 CMake,编译 C++ 代码时去这两个路径下查找头文件
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
# 将 napi_init.cpp 编译成名为 entry 的动态库
# add_library:CMake 编译库文件的命令
# entry:最终生成的动态库名称(编译后会得到libentry.so)
# SHARED:指定生成动态共享库(鸿蒙 NAPI 必须用动态库)
# napi_init.cpp:要编译的 C++ 源文件
add_library(entry SHARED napi_init.cpp)
# 为动态库链接依赖库:让我们的动态库能调用鸿蒙 NAPI 接口,实现 C++ 与 ArkTS 的交互
target_link_libraries(entry PUBLIC libace_napi.z.so)
模块级 build-profile.json5 中 externalNativeOptions 参数是 NDK 工程 C/C++ 文件编译配置的入口
napi_init.cpp 是鸿蒙 NDK 的 “入口文件”,它是 C/C++ 代码 和 ArkTS/JS 代码之间的桥梁,没有它,ArkTS 就调用不了你的 C++ 方法。
它专门负责 3 件事:
- 注册 Native 模块:告诉系统是一个 C++ 动态库
- 绑定 C++ 函数:把你写的 C++ 方法暴露给 ArkTS
- 提供调用入口:让 ArkTS 能像调用普通函数一样调用 C++
#include "napi/native_api.h"
//自定义的 C++ 方法(给 ArkTS 调用)
static napi_value Add(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;
napi_typeof(env, args[0], &valuetype0);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
napi_value sum;
napi_create_double(env, value0 + value1, &sum);
return sum;
}
//模块初始化:实现 ArkTS 接口与 C++ 接口的绑定和映射
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{ "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
// 准备模块加载相关信息,将上述 Init 函数与本模块名等信息记录下来。
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
// 加载 so 时,该函数会自动被调用,将上述 demoModule 模块注册到系统中。
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&demoModule);
}
在 cpp\types\libentry\Index.d.ts 文件中,提供 JS 侧的接口方法
export const add: (a: number, b: number) => number;
在 oh-package.json5 文件中将 index.d.ts 与 cpp 文件关联起来
{
"name": "libentry.so",
"types": "./Index.d.ts",
"version": "1.0.0",
"description": "Please describe the basic information."
}
这些都是由 DevEco Studio 自动生成的,比如我们在 Index.d.ts 中定义一个方法
然后点击 Generate native implementation,它就能在 cpp 中自动生成对应的 C++ 方法和绑定
Node-API
- napi_env:表示 Node-API 执行时的上下文,可以把它理解成 NAPI 给你的一张操作许可证 + 全套工具,所有 NAPI 函数都必须传入它。
- napi_callback_info:代表 ArkTS 调用 C++ 函数时传递过来的所有信息,专门用来获取 ArkTS 传过来的参数。
- napi_value:是一个C的结构体指针,表示一个 ArkTS/JS 对象的引用,可以理解为万能的数据载体,是 NAPI 统一的数据类型,可以表示字符串,数字,布尔,数组,对象,null,undefined 等等,C++ 和 ArkTS 之间传递数据只能用它,不能直接传 int,string,bool,必须包装成 napi_value。
这仨的关系,简言之:
ArkTS 调用 C++ 函数 -> 通过 info 拿到参数列表 -> 参数都是 napi_value 类型 -> 用 env 操作这些 napi_value -> 返回一个 napi_value 给 ArkTS
现在来实现一下上面定义的 NAPI_Global_getLast 方法,用来获取数组的最后一个元素。
static napi_value NAPI_Global_getLast(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);
// 判断是否为数组
bool isArray = false;
napi_is_array(env, args[0], &isArray);
if (isArray) {
// 获取数组长度
uint32_t arrayLength = 0;
napi_get_array_length(env, args[0], &arrayLength);
if (arrayLength > 0) {
// 获取最后一个元素的索引
uint32_t lastIndex = arrayLength - 1;
// 获取数组最后一个元素
napi_value lastElement;
napi_get_element(env, args[0], lastIndex, &lastElement);
// 获取字符串长度
size_t strLen = 0;
napi_get_value_string_utf8(env, lastElement, nullptr, 0, &strLen);
// 读取字符串内容
char resultStr[1024];
napi_get_value_string_utf8(env, lastElement, resultStr, sizeof(resultStr), nullptr);
napi_value returnValue;
// NAPI_AUTO_LENGTH = 让 NAPI 自动计算字符串长度,不用你手动填数字
napi_create_string_utf8(env, resultStr, NAPI_AUTO_LENGTH, &returnValue);
return returnValue;
}
}
return nullptr;
}
常用的 Napi 方法
获取调用信息(函数入口必用)
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
类型判断
napi_is_array:判断是不是数组
bool isArray = false;
napi_is_array(env, args[0], &isArray);
napi_typeof:判断类型
napi_valuetype type;
napi_typeof(env, args[0], &type);
取值
取字符串
char buf[1024];
napi_get_value_string_utf8(env, args[0], buf, sizeof(buf), nullptr);
std::string cppStr = buf;
取数字
double num;
napi_get_value_double(env, args[0], &num);
取整数
int num;
napi_get_value_int32(env, args[0], &num);
取布尔值
bool b;
napi_get_value_bool(env, args[0], &b);
创建值
// 创建数字
napi_value dNum;
napi_create_double(env, 100, &dNum);
// 创建整数
napi_value num;
napi_create_int32(env, 10, &num);
// 创建字符串
napi_value str;
napi_create_string_utf8(env, "Hello", NAPI_AUTO_LENGTH, &str);
// 创建布尔值
napi_value b;
napi_create_boolean(env, true, &b);
// 创建对象
napi_value obj;
napi_create_object(env, &obj);
// 创建数组
napi_value arr;
napi_create_array(env, &arr);
数组操作
// 获取数组长度
uint32_t len;
napi_get_array_length(env, arr, &len);
// 获取数组第 index 个元素
napi_value elem;
napi_get_element(env, arr, index, &elem);
// 设置数组第 index 个元素
napi_set_element(env, arr, index, elem);
对象操作
export const handleUser: (user: UserInfo) => UserInfo;
export interface UserInfo {
name: string;
age: number;
}
// ArkTS对象 → C++结构体
struct UserInfo {
std::string name;
int32_t age;
};
UserInfo ParseUser(napi_env env, napi_value object) {
UserInfo info{};
napi_value nameVal, ageVal;
// 读取 name
napi_get_named_property(env, object, "name", &nameVal);
char nameBuff[64];
size_t len;
napi_get_value_string_utf8(env, nameVal, nameBuff, sizeof(nameBuff), &len);
info.name = nameBuff;
// 读取 age
napi_get_named_property(env, object, "age", &ageVal);
napi_get_value_int32(env, ageVal, &info.age);
return info;
}
// C++ 结构体 -> ArkTs 对象
napi_value WrapUser(napi_env env, const UserInfo &info) {
napi_value jsObject;
napi_create_object(env, &jsObject);
// 设置 name
napi_value nameVal;
napi_create_string_utf8(env, info.name.c_str(), NAPI_AUTO_LENGTH, &nameVal);
napi_set_named_property(env, jsObject, "name", nameVal);
// 设置 age
napi_value ageVal;
napi_create_int32(env, info.age, &ageVal);
napi_set_named_property(env, jsObject, "age", ageVal);
return jsObject;
}
static napi_value NAPI_Global_handleUser(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);
// 解析入参
UserInfo userInfo = ParseUser(env, args[0]);
userInfo.age += 1;
userInfo.name = "XZJ";
return WrapUser(env, userInfo);
}