乐趣区

关于openharmony:三方库移植之NAPI开发1Hello-OpenHarmony-NAPI

本文通过一个 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 接口是 getHelloString
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_addon_register_func
// 2. 指定 NAPI 模块注册对外接口的处理函数,具体扩大的接口在该函数中申明
// 模块对外接口注册函数为 registerFunc
static 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 接口是 getHelloString
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 这两个头文件

    • 在 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 模块注册对外接口的处理函数,具体扩大的接口在该函数中申明
// 模块对外接口注册函数为 registerFunc
static 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
@Component
struct 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 ES OpenSL ES 是一个嵌入式跨平台的音频解决库。
zlib Zlib 是基于 C /C++ 语言实现的一个通用的数据压缩库。
EGL EGL 是渲染 API 与底层原生窗口零碎之间的一种规范的软件接口。
OpenGL ES OpenGL ES 是一个嵌入式跨平台的为 3D 图形处理硬件指定规范的软件接口。

学习材料

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

编译时模块配置规定

编译时部件配置规定

编译时子系统配置规定

编译时产品配置规定

退出移动版