一、什么是NAPI?

NAPI
Node-API, 曾用名NAPI(本文全称NAPI),是OpenHarmony中提供ArkTS/JS与C/C++跨语言调用的接口,是NDK接口中的一部分。该接口是在Node.js提供的Node-API基础上扩展而来,但与Node.js中的Node-API不完全兼容。

OpenHarmony Node-API是基于Node.js 12.x LTS的Node-API规范扩展开发的机制,为开发者提供了ArkTS/JS与C/C++模块之间的交互能力。它提供了一组稳定的、跨平台的API,可以在不同的操作系统上使用。

二、为什么使用NAPI?

一般情况下OpenHarmony应用开发使用ArkTS/JS语言,但部分场景由于性能、效率等要求,比如游戏、物理模拟等,需要依赖使用现有的C/C++库。Node-API规范封装了I/O、CPU密集型、OS底层等能力并对外暴露ArkTS/JS接口,从而实现ArkTS/JS和C/C++的交互。主要场景如下:

  • 系统可以将框架层丰富的模块功能通过ArkTS/JS接口开放给上层应用
  • 应用开发者也可以选择将一些对性能、底层系统调用有要求的核心功能用C/C++封装实现,再通过ArkTS/JS接口使用,提高应用本身的执行效率。

2.1、新增NAPI与NativeC++工程的不同

  • 新增NAPI适用于设备开发、系统开发,可以提供设备支持的sdk给开发者使用。NativeC++仅适用于应用开发。

  • 新增的NAPI可以和其他系统NAPI一样配置预加载,应用加载对应so几乎不耗时。NativeC++需要使用时加载,自定义so无法配置预加载。

  • 新增NAPI由于写在源码中,可以调用inner_api,权限更大,还可以配置自定义权限,ability等。NativeC++无法使用inner_api,仅使用系统暴露的接口。

基于此,本文将介绍如何在系统层面使用NAPI将框架层丰富的模块功能通过ArkTS/JS接口开放给上层应用

三、如何使用dts2cpp工具新增NAPI供上层应用使用

3.1、dts2cpp工具介绍及使用方法

简介

dts2cpp工具,它可以根据用户指定路径下的ts(typescript)接口文件一键生成NAPI框架代码、业务代码框架、GN文件等。在开发JS应用与NAPI间接口时,底层框架开发者无需关注js语法、C++与JS之间的数据类型转换等上层应用转换逻辑,只关注底层业务逻辑即可,专业的人做专业的事,从而可以大大提高开发效率。

约束

系统:建议Ubuntu 20.04

依赖版本:VS Code 1.62.0

使用方法

生成框架

  1. 安装typescript:在napi_generator/src/cli/dts2cpp/src目录下执行命令:

    npm i typescript

  2. 安装stdio:在napi_generator/src/cli/dts2cpp目录下执行命令:

    npm i stdio

  3. 将待转换的文件@ohos.napitest.d.ts拷贝到napi_generator/src/cli/dts2cpp/src/gen下,并在该目录下新建out目录;@ohos.napitest.d.ts如下所示:

    declare namespace napitest {
      function myAdd(a: number, b: number): number;
      function myAddThirtyThoundTimes(a: number, b: number): number;
    }
    export default napitest;
    
  4. 在napi_generator/src/cli/dts2cpp/src/gen下执行以下命令生成napi框架代码:

    node cmd_gen.js -f @ohos.napitest.d.ts -o out
    

    其中,参数详情如下:

    -f, 待转换的.d.ts文件,若同时转换多个文件,文件之间用“,”隔开;
    

    -d, 根据指定路径转换该文件夹中所有.d.ts文件;

    -i, 可选参数,默认false,待转换.d.ts文件中引用非basic.d.ts的ts文件时打开开关;

    -o, 可选参数,默认为当前目录,指定生成框架代码输出路径;

    -n, 可选参数,默认为uint32_t,指定生成框架代码中number类型全部为指定类型;

    -s, 可选参数,默认为不配置业务代码,指定生成框架代码的业务配置文件,用于粘合工具代码和业务代码的配置。

    备注1:-f与-d两个参数只选其中一个参数即可。

    备注2:若.d.ts文件中声明了其它.d.ts文件,将此类文件放置在待转换.d.ts文件同级目录。

  5. 输出文件如下所示:

    ├── binding.gyp
    ├── BUILD.gn
    ├── bundle.json
    ├── napi_gen.log
    ├── napitest.cpp
    ├── napitest.h
    ├── napitest_middle.cpp
    ├── napitest_middle.h
    ├── test.sh
    ├── tool_utility.cpp
    └── tool_utility.h
    

3.2、NAPI框架生成代码集成到OpenHarmony的方法

场景说明

为了实现工具生成的接口被其它子系统或者应用调用,需将生成的代码编译集成到OpenHarmony系统中,使其生成动态库,供OpenHarmony应用层调用。 本文介绍如何将工具生成的源码利用OpenHarmony编译系统生成动态库供应用层调用,主要是有以下两种方式,分别为增加ohos.build文件方式和增加bundle.json文件方式。

建立模块位置

模块目录理论上可在OpenHarmony工程的任一位置,以5.0.0版本为例,4.1类似。假设OpenHarmony代码库的目录为OHOS_SRC,在OHOS_SRC/myFirstNapi目录下,建测试模块目录:napitest。napitest目录结构如下:

myFirstNapi
├── binding.gyp
├── BUILD.gn
├── bundle.json
├── napi_gen.log
├── napitest.cpp
├── napitest.h
├── napitest_middle.cpp
├── napitest_middle.h
├── test.sh
├── tool_utility.cpp
└── tool_utility.h

其中bundle.json为新增的编译配置文件,其它为工具生成的代码。

编译修改点

修改bundle.json文件

其中destPath选项中的"//foundation/napitest"指的是napitest目录,":napitest"指的是上面BUILD.gn中的目标ohos_shared_library("napitest")。

{
  "name": "@ohos/napitest",
  "description": "napitest provides atomic capabilities",
  "version": "5.0.0",
  "license": "Apache License 2.0",
  "publishAs": "code-segment",
  "segment": {
    "destPath": "myFirstNapi"
  },
  "dirs": {},
  "scripts": {},
  "component": {
    "name": "napitest",
    "subsystem": "napitest",
    "features": [],
    "adapted_system_type": [
      "standard"
    ],
    "rom": "10000KB",
    "ram": "10000KB",
    "deps": {
      "components": [
        "napi",
        "ipc_core",
        "libhilog"
      ],
      "third_party": [
        "node"
      ]
    },
    "build": {
      "sub_component": [
        "//myFirstNapi:napitest"
      ],
      "inner_kits": [
        {
          "header": {
            "header_base": "//myFirstNapi",
            "header_files": [
              "tool_utility.h",
              "napitest.h",
              "napitest_middle.h"
            ]
          },
          "name": "//myFirstNapi:napitest"
        }
      ]
    }
  }
}

修改BUILD.gn文件

删除ohos_shared_library("napitest")中的deps,并新增external_deps = [ "napi:ace_napi" ] ,修改后的BUILD.gn文件内容如下所示:

import("//build/ohos.gni")

ohos_shared_library("napitest")
{
    sources = [
        "napitest_middle.cpp",
        "napitest.cpp",
        "tool_utility.cpp",
    ]
    include_dirs = [
        ".",
        "//third_party/node/src",
    ]
    external_deps = [ "napi:ace_napi" ]
    remove_configs = [ "//build/config/compiler:no_rtti" ]
    cflags=[
    ]
    cflags_cc=[
        "-frtti",
    ]
    ldflags = [
    ]
  
    relative_install_dir = "module"
    part_name = "napitest"
    subsystem_name = "napitest"
}

修改napitest.cpp文件

在myAdd方法、myAddThirtyThoundTimes方法中增加业务逻辑:

#include "napitest.h"
#include "napitest_middle.h"


namespace napitest {
namespace napitest_interface {

bool myAdd(NUMBER_TYPE_1& a, NUMBER_TYPE_2& b, NUMBER_TYPE_3& out)
{
    out = a + b;
    return true;
}


bool myAddThirtyThoundTimes(NUMBER_TYPE_4& a, NUMBER_TYPE_5& b, NUMBER_TYPE_6& out)
{
    for (int i = 0; i < 300000; i++) {
        out += a + b;
    }
    return true;
}

}
}

增加子系统

在源码/build/subsystem_config.json中增加子系统选项。如下所示:

 "napitest": {
    "path": "myFirstNapi",
    "name": "napitest"
 }

img

添加功能模块

在产品配置中添加上述子系统的功能模块,编译到产品产出文件中,例如在源码vendor/hihope/rk3568/config.json中增加part选项,其中the first napitest就是BUILD.gn文件中的subsystem_name,第二个napitest就是BUILD.gn文件中的part_name。

{
      "subsystem": "napitest",
      "components": [
        {
          "component": "napitest",
          "features": []
        }
      ]
}

img

编译验证

编译成功后,就会在 /out/产品名/packages/phone/system/lib/module/ 生成libnapitest.z.so,如下所示:

/out/rk3568/packages/phone/system/lib/module

img

调用

NAPI框架代码生成后,系统框架开发者进行二次开发后,即可集成到OpenHarmony编译系统,生成对应的库文件,供应用开发者调用接口。

3.3、NAPI框架生成结果验证

准备

  1. 硬件:rk3568开发套件。

  2. 系统镜像: 上一步生成的镜像,

  3. DevEco 4.1Release

  4. 安装镜像环境:将out/rk3568/packages/phone目录下的images镜像文件下载并烧录到开发板上。

  5. DevEco 4.1Release新建一个OpeHarmony项目,API选择11,打开项目生成的Index.ets文件

修改点1:扩展SDK接口

  1. 查看SDK目录:打开DevEco Studio ,点击 Tools -> SDK Manager -> SDK

    img

  2. @ohos.napitest.d.ts文件拷贝到应用所使用的sdk目录下 的ets\api

    img

修改点2:修改@ohos.napitest.d.ts,增加描述

不加上描述IDE扫描不到,会报错,修改后需要重启IDE才能加载不报红

/**
 *
 *
 * @since 9
 * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
 */
declare namespace napitest {
  function myAdd(a: number, b: number): number;
  function myAddThirtyThoundTimes(a: number, b: number): number;
}
export default napitest;

img

修改点3:增加新接口调用

其中修改index.ets文件内容如下:

import napitest from '@ohos.napitest'

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  jsAddThirtyThoundTimes(a: number, b: number): number {
    let out = 0;
    for (let i = 0; i < 300000; i++) {
      out += a + b;
    }
    return out;
  }

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            let start = new Date().getTime();
            console.log(start.toString());
            let b = this.jsAddThirtyThoundTimes(2, 3);
            console.log(b.toString());
            let time = new Date().getTime() - start;
            console.log(time.toString());

            start = new Date().getTime();
            console.log(start.toString());
            let a = napitest.myAddThirtyThoundTimes(2, 3);
            console.log(a.toString());
            time = new Date().getTime() - start;
            console.log(time.toString());
          })

        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            let start = new Date().getTime();
            console.log(start.toString());
            let a = napitest.myAddThirtyThoundTimes(2, 3);
            console.log(a.toString());
            let time = new Date().getTime() - start;
            console.log(time.toString());

            start = new Date().getTime();
            console.log(start.toString());
            let b = this.jsAddThirtyThoundTimes(2, 3);
            console.log(b.toString());
            time = new Date().getTime() - start;
            console.log(time.toString());
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

运行

点击run,将应用安装到设备上

img

查看结果

分别点击上下两个Hello World,可以看到日志有如下打印。可以看到同样是300000次运算,c++快了很多。

img

四、参考

NDK开发导读:https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/napi/ndk-development-overview.md

Node-API简介:https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/napi/napi-introduction.md

napi_generator简介:https://gitee.com/openharmony/napi/_generator

三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI:https://blog.csdn.net/maniuT/article/details/137689680

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐