本文通过一个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/1...

增加子系统、组件

间接在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/1...

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

  • 增量编译命令

     ./build.sh --product-name rk3568 --ccache --build-target=hellonapi --target-cpu arm64

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

调用接口

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/1...

调用接口

第一步:调用形式和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反对的规范库

名称简介
规范C库libc、libm、libdl组合实现C11规范C库。
规范C++库libc++ 是C++规范库的一种实现。
OpenSL ESOpenSL ES是一个嵌入式跨平台的音频解决库。
zlibZlib是基于C/C++语言实现的一个通用的数据压缩库。
EGLEGL是渲染API与底层原生窗口零碎之间的一种规范的软件接口。
OpenGL ESOpenGL ES是一个嵌入式跨平台的为 3D 图形处理硬件指定规范的软件接口。

学习材料

OpenHarmony 源码解析之NAPI框架外部实现剖析

编译时模块配置规定

编译时部件配置规定

编译时子系统配置规定

编译时产品配置规定