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.更多接口说明参照官网
华为开发者学堂