本文通过一个Hello OpenHarmony NAPI样例讲述了NPAI接口开发基础知识。开发基于最新的OpenHarmony3.2Beta3版本及其对应SDK。标准系统开发板为润和软件dayu200。
将C/C++ 三方库移植到OpenHarmony标准系统后,需要通过NAPI框架将其C/C++ 接口转换成JS/ETS接口给应用层调用。
通过本文您将熟悉:
-
OpenHarmony 标准系统应用开发基于ArkUI框架,开发语言使用JS/eTS。部分业务场景依赖使用现有的C/C++ 库,或为了获取更高的性能。OpenHarmony提供NAPI机制,用于规范封装IO、CPU密集型、OS底层等能力并对外暴露JS接口,通过NAPI实现JS和C/C++代码的互相访问.
-
OpenHarmony 中的 N-API 定义了由 JS/ETS 语言编写的代码和 native 代码(使用 C/C++ 编写)交互的方式,由 Node.js N-API 框架扩展而来。
- N-API:Native Application Programming Interface(本地应用程序接接口)
- 什么是Node.js N-API 框架
Node.js N-API为开发者提供了一套C/C++ API用于开发Node.js的Native扩展模块。从Node.js 8.0.0开始,N-API以实验性特性作为Node.js本身的一部分被引入,并且从Node.js 10.0.0开始正式全面支持N-API。
添加OpenHarmony自定义子系统、组件、模块
- 这部分内容涉及三方库移植,为便于本篇NAPI基础的学习。笔者在此自定义一个子系统用于开发NAPI。如在已存在的子系统组件中添加扩展NAPI,则跳过此步。
- 需要准备好OpenHarmonyBeta3源码和编译环境
添加子系统、组件
直接在OpenHarmony源码根目录创建子系统文件夹,取名mysubsys。并在目录下添加子系统的构建配置文件ohos.build
完整内容如下:
{
"subsystem": "mysubsys",
"parts": {
"hello": {
"module_list": [
"//mysubsys/hello/hellonapi:hellonapi"
],
"inner_kits": [
],
"system_kits": [
],
"test_list": [
]
}
}
}
- 另外ohos.build里面不支持加注释,后面编译的时候会莫名其妙报错。别问,问就是笔者踩过坑了。(好像也没必要加注释)
需要明白以下知识点:
"subsystem": "mysubsys",
- subsystem后面的mysubsy是子系统的名称。
"parts": {
"hello": {
}
}
- hello是组件名称,被mysubsys子系统包含
"module_list": [
"//mysubsys/hello/hellonapi:hellonapi"
- hellonapi是模块名,被hello组件包含。
接着将子系统配置到源码下build\subsystem_config.json文件,在该文件中插入如下内容。
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys",
"dir": ""
}
- OpenHarmony系统架构中,子系统是一个逻辑概念,它具体由对应的组件构成。组件是对子系统的进一步拆分,可复用的软件单元,它包含源码、配置文件、资源文件和编译脚本;能独立构建,以二进制方式集成,具备独立验证能力的二进制单元。
本示例按子系统system > 组件part > 组件module 创建了3级目录
mysubsys
├── hello
│ └── hellonapi
│ ├── BUILD.gn
│ └── hellonapi.cpp
└── ohos.build
源码实现
最后在组件目录下中创建代码文件hellonapi.cpp
完整内容如下:
#include <string.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"
static napi_value getHelloString(napi_env env, napi_callback_info info) {
napi_value result;
std::string words = "Hello OpenHarmony NAPI";
NAPI_CALL(env, napi_create_string_utf8(env, words.c_str(), words.length(), &result));
return result;
}
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()
{
napi_module_register(&hellonapiModule);
}
代码解析如下:
接口业务实现C/C++代码
static napi_value getHelloString(napi_env env, napi_callback_info info) {
napi_value result;
std::string words = "Hello OpenHarmony NAPI";
NAPI_CALL(env, napi_create_string_utf8(env, words.c_str(), words.length(), &result));
return result;
}
添加NAPI接口头文件
NAPI提供了提供了一系列接口函数,声明包含如下2个头文件中,先添加这2个头文件到hellonapi.cpp
#include "napi/native_api.h"
#include "napi/native_node_api.h"
- native_api.h和native_node_api.h这两个头文件
- 在OpenHarmony-3.1-release源码下是在//foundation/ace/napi/interfaces/kits目录下
- 在3.2beta3版本中分别在//foundation/arkui/napi/interfaces/kits和//foundation/arkui/napi/interfaces/inner_api目录下了。
注册NAPI模块、添加接口声明
定义的hellonapi模块,其对应结构体为napi_module。
- 指定当前NAPI模块对应的模块名以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明。
static napi_module hellonapiModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerFunc,
.nm_modname = "hellonapi",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
- NAPI提供DECLARE_NAPI_FUNCTION(name, func)函数用于声明api,传入名称和其他实现函数。在registerFunc函数中添加DECLARE_NAPI_FUNCTION,本例添加了一个getString接口。
- 模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
static napi_value registerFunc(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}
自定义子系统构建
hellonapi编译gn化,新增gn工程构建脚本。
在模块hellonapi目录下新建BUILD.gn文件,内容如下:
gn文件支持注释,以#
开头
import("//build/ohos.gni")
ohos_shared_library("hellonapi") {
include_dirs = [
"//foundation/arkui/napi/interfaces/kits",
"//foundation/arkui/napi/interfaces/inner_api",
"//third_party/node/src"
]
cflags_cc = [
"-Wno-error",
"-Wno-unused-function",
]
sources = [
"hellonapi.cpp"
]
deps = [ "//foundation/arkui/napi:ace_napi" ]
relative_install_dir = "module"
subsystem_name = "mysubsys"
part_name = "hello"
}
修改产品配置
将组件添加到需要的产品配置文件,源码目录下的productdefine/common/products/ohos-arm64.json。
- 插入位置任意,但要注意行尾的逗号,确保格式json文件格式正确。
"parts":{
...
"mysubsys:hello":{},
...
}
- mysubsys是本示例自定义的子系统名称
- hello是自定义子系统下的组件名称
- parts格式如下:
"parts":{
"部件所属子系统名:部件名":{}
}
修改build/subsystem_config.json
新增子系统定义。
- subsystem_config.json文件定义了有哪些子系统以及这些子系统所在文件夹路径,添加子系统时需要说明子系统path与name,分别表示子系统路径和子系统名。
注意json文件也不支持注释!!!
"mysubsys": {
"project": "hmf/mysubsys",
"path": "mysubsys",
"name": "mysubsys"
}
修改vendor/hihope/rk3568/config.json文件
将mysubsys子系统添加至rk3568开发板,在vendor目录下新增产品的定义。
{
"subsystem": "mysubsys",
"components": [
{
"component": "hello",
"features": []
}
]
}
编译烧录
先进行增量编译出子系统的动态库,增量编译没有报错后。再全量编译出镜像,将其烧录到开发板上。
./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64
- 全量编译和烧录
这部分的内容不重复叙述,大家可以参考社区文章。
镜像文件在源码目录下位置如下:
调用接口
full-SDK替换(可选)
从OpenHarmony 3.2 Beta2起,SDK会同时提供Public SDK和Full SDK。通过DevEco Studio默认获取的SDK为Public SDK。
两者差异如下:
- Public SDK
- 面向应用开发者提供,不包含需要使用系统权限的系统接口。通过DevEco Studio默认获取的SDK为Public SDK。
- Full SDK
- 面向OEM厂商提供,包含了需要使用系统权限的系统接口。使用Full SDK时需要手动从镜像站点获取,并在DevEco Studio中替换
笔者使用的DevEco Studio版本为3.0.0.993,即DevEco Studio 3.0。API为API9。
full-SDK替换请参考官方文档: full-SDK替换指南
若提示找不到npm,需要配置一下环境变量,将以下路径添加到环境变量中即可
D:\DevEco Studio\ohos\sdk\ets\build-tools\ets-loader
创建OpenHarmony标准应用
新建项目,选择OpenHarmony。
compile sdk选择9,其他保持默认即可。
插一句题外话,3.1release版本发布的时候。华为是把DevEco Studio分成了OpenHarmony和HarmonyOS两个版本的,现在又合并到一起了。
调用接口
- 调用方式和ArkUI框架提供的API一样,先import引入扩展的NAPI模块,后直接调用。
index.ets内容如下:
import prompt from '@system.prompt'
import hellonapi from '@ohos.hellonapi'
@Entry
@Component
struct HelloNAPI {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button("NAPI: hellonapi.getHelloString()").margin(10).fontSize(24).onClick(() => {
let strFromNAPI = hellonapi.getHelloString()
prompt.showToast({ message: strFromNAPI })
})
}
.width('100%')
.height('100%')
}
}
然后选择自动签名
将应用安装到dayu200开发板上
运行效果如下:
- 也可以参考其他模块的.d.ts创建扩展模块@ohos.hellonapi.d.ts定义文件,放到IDE安装OpenHarmony SDK的目录路径ohos\sdk\ets\3.2.7.5\api下。
- .d.ts文件的命名为@ohos.ohos_shared_library_name.d.ts,ohos_shared_library为BUID.gn文件中定义的动态库名称
@ohos.hellonapi.d.ts内容如下:
declare namespace hellonapi {
function getHelloString(): string;
}
export default hellonapi;
- 其中
@syscap SystemCapability.HiviewDFX.HiAppEvent
语句在.d.ts文件中一定要添加,否则IDE还是会报错找不到该文件。
- 其中
declare namespace hellonapi
的hellonapi是BUILD.gn中的定义的ohos_shared_library_name。
- 其中
function getHelloString(): string;
中的getHelloString()是hellonapi.cpp文件中指定的模块注册对外接口的处理函数
IDE问题扫描如下:
如果不新建@ohos.hellonapi.d.ts放在sdk\ets\3.2.7.5\api,则IDE会报错
标准应用编译不是强依赖OpenHarmony SDK,所以可忽略IDE中告警,直接编译打包hap。但是有的时候IDE会提示找不到@ohos.hellonapi.d.ts,然后有小概率的机会无法安装hap。这个时候就要参考ohos\sdk\ets\3.2.7.5\api下的.d.ts文件编写@ohos.hellonapi.d.ts了。
知识点附送
Native API中支持的标准库
表1 OpenHarmony支持的标准库