2

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

 1 year ago
source link: https://www.51cto.com/article/720422.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

作者:离北况归 2022-10-11 15:04:28
本文通过一个Hello OpenHarmony NAPI样例讲述了NPAI接口开发基础知识。开发基于最新的OpenHarmony3.2Beta3版本及其对应SDK。标准系统开发板为润和软件dayu200。
e74b8e225c5779bd40a177306051812ce82c04.png

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

本文通过一个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规范开发的原生模块扩展开发框架。
#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

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接口是 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开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

注册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开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

注册NAPI模块总结

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony 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
#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

全量编译和烧录。

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

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

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。

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

full-SDK替换请参考官方文档: ​​full-SDK替换指南​​。

若提示找不到npm,需要配置一下环境变量,将以下路径添加到环境变量中即可。

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区
#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

D:\DevEco Studio\ohos\sdk\ets\build-tools\ets-loader。

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

创建OpenHarmony标准应用

新建项目,选择OpenHarmony。

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区
compile sdk选择9,其他保持默认即可。
#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

插一句题外话,3.1release版本发布的时候。华为是把DevEco Studio分成了OpenHarmony和HarmonyOS两个版本的,现在又合并到一起了。感兴趣的读者可以查阅笔者文章 https://ost.51cto.com/posts/11168。

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区
#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

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

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony 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文件中定义的动态库名称。
resize,w_594,h_232

@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 hellonapi和export default hellonapi的hellonapi是BUILD.gn中的定义的ohos_shared_library_name。
  • function getHelloString(): string;中的getHelloString()是hellonapi.cpp文件中指定的模块注册对外接口的处理函数。

则IDE扫描如下:

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

标准应用编译不是强依赖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会报错。

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

第三步:选择自动签名

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区
#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

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

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

运行效果如下:

#打卡不停更#三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI-开源基础软件社区

知识点附送

Native API中支持的标准库

表1 OpenHarmony支持的标准库。

​libc、libm、libdl​​组合实现C11标准C库。

标准C++库

​libc++​​ 是C++标准库的一种实现。

OpenSL ES

​OpenSL ES​​是一个嵌入式跨平台的音频处理库。

​Zlib​​是基于C/C++语言实现的一个通用的数据压缩库。

​EGL​​是渲染API与底层原生窗口系统之间的一种标准的软件接口。

OpenGL ES

​OpenGL ES​​是一个嵌入式跨平台的为 3D 图形处理硬件指定标准的软件接口。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。

责任编辑:jianghua 来源: 51CTO开源基础软件社区

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK