1.Node-api组成架构
为了应对日常开发经的网络通信、串口访问、多媒体解码、传感器数据收集等模块,这些模块大多数是使用c++接口实现的,arkts侧如果想使用这些能力,就需要使用node-api这样一套接口去桥接c++代码。Node-api整体的架构图如下:
Node-api接口是基于node.js的一个扩展,所以在平常开发过程也可以参照node.js官网,像接口实现的功能,入参都是类似的。
Framework模块介绍
1)ModuleManager是管理对象的模块,当arkts侧调用c++时,会加载Native侧的模块到ModuleMangger,并转化为arkts对象返回上层。
2)ScopeManager:用于管理napi_value生命周期,napi_value是Node-api独特的数据类型,类似于ArkTs中的number、String等各种参数类型的统一表示形式,在Native侧代码开发中不需要感知不同类型的数据类型,统一都是napi_value。
3)ReferenceManager:用于管理引用,开发时经常会有一些跨线程的场景,这个时候就需要创建引用(napi_ref),否则就就会被GC回收掉。napi_ref用于指向napi_value,允许用户管理napi_value值的生命周期。
3)Native Engine的作用是统一ArkTS引擎在Node-API层的接口行为。
4)方舟运行时,也就是ArkTS引擎,整个Node-API模块都是跑在方舟运行时的。
2.Napi和native的交互流程
主要分为两步:模块初始化和函数调用
模块初始化
ArkTS在import一个so库的时候,会先找到ArkTS引擎,ArkTS引擎会加载模块信息到ModuleMananger,其实就是对应的dlopen函数(首次调用时加载,多次调用回去缓存查找)。之后ModuleManager会把模块信息返回给ArkTS引擎。ArkTS引擎拿到模块信息后,在native层触发模块注册,来初始化模块。注册完成之后通过底层框架返回给上层一个ArkTS对象,这个对象上挂在着c/c++侧的方法。我们拿到arkts对象后,就可以去调用c/c++的方法了。
函数调用
当arkts侧通过上述import返回的对象的调用方法时,ArkTS引擎会找到并调用对应的C/C++方法
3.Node-API支持的数据类型
napi_status:枚举数据类型,表示Node-API接口返回的状态信息。每当调用一个Node-API函数,都会返回该值,表示操作成功与否的相关信息。枚举信息如下:
typedef enum{
napi_ok,
napi_invalid_arg,
napi_object_expected,
napi_string_expected,
napi_name_expected,
...
napi_would_deadlock
} napi_status;
if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
return nullptr;
}
Node-API将基本数据类型公开为各种API使用的抽象。这些API应该被视为不透明的,只能通过其他Node-API调用进行自省。
- napi_value:在Native侧代码开发中不需要感知不同类型的数据,统一都是napi_value
举例说明:napi_value napiResult; napi_create_double(env, 2.0, &napiResult); - napi_env:Native侧函数入参,表示Napi-API执行时的上下文。退出native侧的时,napi_env将失效
- napi_callback_info: Native侧函数的入参,保存了ArkTS侧的参数信息,用于传递给napi_get_cb_info()函数获取ArkTS侧入参信息。
举例说明:static napi_value MyHypot(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2] = {nullptr}; // 从info中获取参数信息到参数数组args[] napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); }
4.Node-API接口
Node-API接口在Node.js提供的原生模块基础上,目前只支持部分接口。常用的部分接口介绍如下:
- napi_get_cb_info: 从给定的napi_callback_info中获取有关调用的详细信息,如参数和this指针
static napi_value MyHypot(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2] = {nullptr}; // 从info中获取参数信息到参数数组args[] napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); }
-
napi_get_value_double: 获取给定ArkTS的number类型
double valueX = 0.0;
//获取args[0]存储的参数信息并赋值给valueX,args[]中存储接受ArkTs侧参数
napi_get_value_double(env, args[0], &valueX); -
napi_create_string_utf8: 通过utf8编码的c字符串数据创建ArtTS侧String类型的数据
std::string str = "temp"; napi_value sum; //用于存储转换类型后的string类型数据 napi_create_string_utf8(env, str.c_str(), str.length(). &sum);
5.案例讲解说明交互流程
目录说明
CPP目录
types/index.d.ts:定义了c++侧需要暴漏在ArkTS侧的接口
oh-package.json5是描述index.d.ts文件的
napi_init.cpp文件在里面做Native模块的注册、类型的转换以及大部分业务逻辑
ets目录
page/index.ets 里面有ArkTS调用C++
src目录
build-profile.json5:主要是配置构建信息(主要是cmakelist的构建路径,构建的时候能够找到cmakelist文件)
oh-package.json5:主要配置了一些模块本身的信息和依赖的工程
6.官方代码案例
基于Node-API开发业务功能
static napi_value MyHypot(napi_env env, napi_callback_info info)
{
if ((nullptr == env) || (nullptr == info)) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot", "env or exports is null");
return nullptr;
}
// Number of parameters.
size_t argc = 2;
// Declare parameter array.
napi_value args[2] = { nullptr };
// Gets the arguments passed in and puts them in the argument array.
if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot", "api_get_cb_info failed");
return nullptr;
}
// Converts arguments passed in to type double.
double valueX = 0.0;
double valueY = 0.0;
if (napi_ok != napi_get_value_double(env, args[0], &valueX) ||
napi_ok != napi_get_value_double(env, args[1], &valueY)) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot", "napi_get_value_double failed");
return nullptr;
}
// The hypot method of the C standard library is called to perform the calculation.
double result = hypot(valueX, valueY);
napi_value napiResult;
if (napi_ok != napi_create_double(env, result, &napiResult)) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "MyHypot", "napi_create_double failed");
return nullptr;
}
return napiResult;
}
接口映射
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
if ((nullptr == env) || (nullptr == exports)) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "env or exports is null");
return exports;
}
napi_property_descriptor desc[] = {
{ "myHypot", nullptr, MyHypot, nullptr, nullptr, nullptr, napi_default, nullptr }
};
if (napi_ok != napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)) {
OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed");
return nullptr;
}
return exports;
}
EXTERN_C_END
模块注册
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "hello",
.nm_priv = ((void *)0),
.reserved = { 0 }
};
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
napi_module_register(&demoModule);
}
模块构建配置
cmakelist.txt文件:
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(NativeTemplateDemo)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(
${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include
)
find_library(
# Sets the name of the path variable.
hilog-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
hilog_ndk.z
)
add_library(hello SHARED hello.cpp)
target_link_libraries(hello PUBLIC ${hilog-lib} libace_napi.z.so libc++.a)
根目录下的build-profile.json5
{
"apiType": 'stageMode',
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"abiFilters": [
"arm64-v8a",
"x86_64"
],
"cppFlags": "",
}
},
"targets": [
{
"name": "default",
"runtimeOS": "HarmonyOS"
}
]
}
导出native接口
/entry/oh-package.json5
{
"license": "",
"devDependencies": {
"libhello.so": "file:./src/main/cpp/types/libhello"
},
"author": "",
"name": "entry",
"description": "Please describe the basic information.",
"main": "",
"version": "1.0.0",
"dependencies": {}
}
7.更多接口说明参照官网
华为开发者学堂