写点什么

三方库移植之 NAPI 开发 [1]—Hello OpenHarmony NAPI

作者:离北况归
  • 2022-10-12
    江苏
  • 本文字数:6925 字

    阅读完需:约 23 分钟

本文通过一个 Hello OpenHarmony NAPI 样例讲述了 NPAI 接口开发基础知识。开发基于最新的 OpenHarmony3.2Beta3 版本及其对应 SDK。标准系统开发板为润和软件 dayu200。


将 C/C++ 三方库移植到 OpenHarmony 标准系统后,需要通过 NAPI 框架将其 C/C++ 接口转换成 JS/ETS 接口给应用层调用。




通过本文您将熟悉:


  • 如何注册 NAPI 模块及接口。

  • 如何在 ArkUI eTS 代码中调用扩展的 NAPI 接口。

  • full-SDK 的替换。

什么是 NAPI

  • NAPI(Native API)组件是一套对外接口基于 Node.js N-API 规范开发的原生模块扩展开发框架。



::: hljs-center


NAPI 组件架构图


:::


  • OpenHarmony 标准系统应用开发基于 ArkUI 框架,开发语言使用 JS/eTS。部分业务场景依赖使用现有的 C/C++ 库,或为了获取更高的性能。OpenHarmony 提供 NAPI 机制,用于规范封装 IO、CPU 密集型、OS 底层等能力并对外暴露 JS 接口,通过 NAPI 实现 JS 和 C/C++代码的互相访问.

  • 例如: 钟禄平和林嘉诚老师在如何在OpenHarmony上使用SeetaFace2人脸识别库?一文中,重点讲解了 NAPI 接口如何实现 OpenCV 以及 SeetaFace 的调用。一句话概括就是,钟禄平和林嘉诚老师讲述了移植了三方库后通过 NAPI 将库的 C/C++接口变成 JS/ETS 接口给应用层调用。

  • 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 源码和编译环境

  • 笔者的编译环境为 WSL2+Ubuntu18.04+vscode,搭建笔者一样的编译环境搭建可以参考 https://ost.51cto.com/posts/17164

添加子系统、组件

直接在 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        -- 组件module目录 │       └── hellonapi.cpp   └── ohos.build
复制代码

源码实现

最后在组件目录下中创建代码文件 hellonapi.cpp


完整内容如下:


#include <string.h>#include "napi/native_node_api.h"#include "napi/native_api.h"

// 接口业务实现C/C++代码// std::string 需要引入string头文件,#include <string>// 该napi_module对外具体的提供的API接口是 getHelloStringstatic 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_addon_register_func// 2.指定NAPI模块注册对外接口的处理函数,具体扩展的接口在该函数中声明// 模块对外接口注册函数为registerFuncstatic napi_value registerFunc(napi_env env, napi_value exports){ static napi_property_descriptor desc[] = {
// 声明该napi_module对外具体的提供的API为getHelloString DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
}; NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); return exports;}

// 注册NAPI模块// 1.先定义NAPI模块,指定当前NAPI模块对应的模块名// 以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明// nm_modname: NAPI模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'// 示例对应hap应用中eTS代码需要包含import hellonapi from '@ohos.hellonapi'// 以下的出现的hellonapi都为注册的NAPI模块名static napi_module hellonapiModule = { .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr,
// registerFunc是NAPI模块对外接口注册函数 .nm_register_func = registerFunc,
.nm_modname = "hellonapi", .nm_priv = ((void*)0), .reserved = { 0 },};

// 3.NAPI模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。// register module,设备启动时自动调用此constructor函数,把定义的模块注册到OpenHarmony中。// 以下出现的hellonapi都是注册的NAPI模块名extern "C" __attribute__((constructor)) void hellonapiModuleRegister(){ // napi_module_register是ohos的NAPI组件提供的模块注册函数 napi_module_register(&hellonapiModule);}
复制代码


代码解析如下

接口业务实现 C/C++代码

// 接口业务实现C/C++代码// std::string 需要引入string头文件,#include <string>// 该napi_module对外具体的提供的API接口是 getHelloStringstatic 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 这两个头文件

  • 在 OpenHarmony3.1release 源码中在//foundation/ace/napi/interfaces/kits 目录下

  • 在 OpenHarmony3.2beta3 源码中分别在//foundation/arkui/napi/interfaces/kits 和//foundation/arkui/napi/interfaces/inner_api 目录下了。


注册 NAPI 模块、添加接口声明

定义的 hellonapi 模块,其对应结构体为 napi_module。


  • 指定当前 NAPI 模块对应的模块名

  • 模块注册对外接口的处理函数,具体扩展的接口在该函数中声明。


// 注册对外接口的处理函数napi_addon_register_func// 2.指定NAPI模块注册对外接口的处理函数,具体扩展的接口在该函数中声明// 模块对外接口注册函数为registerFuncstatic napi_value registerFunc(napi_env env, napi_value exports){    static napi_property_descriptor desc[] = {
// 声明该napi_module对外具体的提供的API为getHelloString DECLARE_NAPI_FUNCTION("getHelloString", getHelloString),
}; NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); return exports;}

// 注册NAPI模块// 1.先定义NAPI模块,指定当前NAPI模块对应的模块名// 以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明// nm_modname: NAPI模块名称,对应eTS代码为import nm_modname from '@ohos.ohos_shared_library_name'// 示例对应hap应用中eTS代码需要包含import hellonapi from '@ohos.hellonapi'// 以下的出现的hellonapi都为注册的NAPI模块名static napi_module hellonapiModule = { .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr,
// registerFunc是该自定义的NAPI模块对外接口注册函数 .nm_register_func = registerFunc,
.nm_modname = "hellonapi", .nm_priv = ((void*)0), .reserved = { 0 },};

// 3.NAPI模块定义好后,调用ohos的NAPI组件提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。// register module,设备启动时自动调用constructor函数,把定义的模块注册到OpenHarmony中。// 以下出现的hellonapi都是注册的NAPI模块名extern "C" __attribute__((constructor)) void hellonapiModuleRegister(){ // napi_module_register(napi_module* mod)是ohos的NAPI组件提供的模块注册函数 napi_module_register(&hellonapiModule);}
复制代码


  • napi_module_register(napi_module* mod)是 ohos 的 NAPI 组件提供的模块注册函数。

  • 该函数在源码目录下 foundation/arkui/napi/native_engine/native_node.cpp


注册 NAPI 模块总结

自定义子系统构建

hellonapi 编译 gn 化,新增 gn 工程构建脚本。

在模块 hellonapi 目录下新建 BUILD.gn 文件,内容如下:


gn 文件支持注释,以#开头


import("//build/ohos.gni")
#ohos_shared_library()中的hellonapi决定了生成动态库的名称,增量编译阶段生成动态库libhellonapi.z.so
ohos_shared_library("hellonapi") {
include_dirs = [
#NAPI头文件目录 "//foundation/arkui/napi/interfaces/kits", "//foundation/arkui/napi/interfaces/inner_api",
#根据增量编译阶段报错添加的头文件目录 "//third_party/node/src" ]
#根据增量编译时clang编译器报警,添加的cflag cflags_cc = [ #编译时报错提示"-Werror",则加上"-Wno-error" "-Wno-error", #编译时报错提示"-Wunused-function",则加上"-Wno-unused-function" "-Wno-unused-function", ] #编译需要的源文件 sources = [ "hellonapi.cpp" ]
#指定编译依赖libace_napi.z.so动态库 deps = [ "//foundation/arkui/napi:ace_napi" ]
#指定库生成的路径 #libhellonapi.z.so会安装在rk3568开发板的system/lib/module目录下 relative_install_dir = "module" #子系统名称是mysubsys subsystem_name = "mysubsys" #组件名称是hello 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"}
复制代码


  • "path": "mysubsys",表示子系统路径

  • "name": "mysubsys"表示子系统名称

修改 vendor/hihope/rk3568/config.json 文件

将 mysubsys 子系统添加至 rk3568 开发板,在 vendor 目录下新增产品的定义。


    {      "subsystem": "mysubsys",      "components": [        {          "component": "hello",          "features": []        }      ]    }
复制代码


  • "subsystem": "mysubsys",表示添加的子系统是 mysubsys

  • "component": "hello",表示添加的子系统中包含的组件名称是 hello

编译烧录

关于这部分的内容可以参考笔者三方库移植系列文章 https://ost.51cto.com/posts/16848#OpenHarmonySpeexdsp_25


先进行增量编译出子系统的动态库,增量编译没有报错后。再全量编译出镜像,将其烧录到开发板上


  • 增量编译命令


 ./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64
复制代码



  • 全量编译和烧录这部分的内容不重复叙述,大家可以参考社区文章 https://ost.51cto.com/posts/16203 镜像文件在源码目录下位置如下:

调用接口

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 两个版本的,现在又合并到一起了。感兴趣的读者可以查阅笔者文章 https://ost.51cto.com/posts/11168

调用接口

第一步:调用方式和 ArkUI 框架提供的 API 一样,先 import 引入扩展的 NAPI 模块,后直接调用。



index.ets 内容如下:


import prompt from '@system.prompt'
// 引入扩展的NAPI模块 // 在hellonapi.cpp文件中定义nm_modname(模块名称)为hellonapi// 在BUILD.gn文件中定义ohos_shared_library结构体名称为hellonapi// 所以是import hellonapi from '@ohos.hellonapi'import hellonapi from '@ohos.hellonapi'

@Entry@Componentstruct HelloNAPI { build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { Button("NAPI: hellonapi.getHelloString()").margin(10).fontSize(24).onClick(() => {
// hellonapi.cpp对外具体的提供的API是getHelloString let strFromNAPI = hellonapi.getHelloString()
prompt.showToast({ message: strFromNAPI }) }) } .width('100%') .height('100%') }}
复制代码


第二步(可选):参考其他模块的.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;    /**     *      *     * @since 9     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore     */
}export default hellonapi;
复制代码


  • @since 9表示 API 的版本为 9

  • @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore语句在.d.ts 文件中一定要添加,否则 IDE 还是会报错找不到该文件。

  • declare namespace hellonapiexport default hellonapihellonapi是 BUILD.gn 中的定义的 ohos_shared_library_name。

  • function getHelloString(): string;中的getHelloString()是 hellonapi.cpp 文件中指定的模块注册对外接口的处理函数


则 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 了。如果不新建 @ohos.hellonapi.d.ts 放在 sdk\ets\3.2.7.5\api,则 IDE 会报错


第三步:选择自动签名




第四步:将应用安装到 dayu200 开发板上



运行效果如下:


知识点附送

Native API 中支持的标准库

表 1 OpenHarmony 支持的标准库


学习资料

OpenHarmony 源码解析之NAPI框架内部实现分析


编译时模块配置规则


编译时部件配置规则


编译时子系统配置规则


编译时产品配置规则

用户头像

离北况归

关注

还未添加个人签名 2022-03-26 加入

OpenHarmony啃论文俱乐部PIMF团队。 位于南京一学生,可私信。

评论

发布
暂无评论
三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI_OpenHarmony_离北况归_InfoQ写作社区