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

文章目录
  • 三方库移植之NAPI开发—Hello OpenHarmony NAPI

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

    ​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​​(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架。

    三方库移植之NAPI开发—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。

    • 这部分内容涉及三方库移植,为便于本篇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++代码
    // 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提供了提供了一系列接口函数,声明包含如下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开发—Hello OpenHarmony 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开发—Hello OpenHarmony NAPI

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

    在模块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":{
    "部件所属子系统名:部件名":{}
    }

    新增子系统定义。

    • subsystem_config.json文件定义了有哪些子系统以及这些子系统所在文件夹路径,添加子系统时需要说明子系统path与name,分别表示子系统路径和子系统名。

    注意json文件也不支持注释!!!

    "mysubsys": {
    "project": "hmf/mysubsys",
    "path": "mysubsys",
    "name": "mysubsys"
    }
    • "path": "mysubsys",表示子系统路径。
    • "name": "mysubsys"表示子系统名称。

    将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开发—Hello OpenHarmony NAPI

    全量编译和烧录。

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

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

    从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开发—Hello OpenHarmony NAPI
    三方库移植之NAPI开发—Hello OpenHarmony NAPI

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

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

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

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

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

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

    新建项目,选择OpenHarmony。

    三方库移植之NAPI开发—Hello OpenHarmony NAPI
    compile sdk选择9,其他保持默认即可。
    三方库移植之NAPI开发—Hello OpenHarmony NAPI

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

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

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

    三方库移植之NAPI开发—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文件中定义的动态库名称。

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

    @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开发—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开发—Hello OpenHarmony NAPI

    第三步:选择自动签名

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

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

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

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

    运行效果如下:

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

    表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 图形处理硬件指定标准的软件接口。

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

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

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

    © 版权声明

    相关文章