OpenHarmony5.0release设备开发基础之新增NAPI\NAPI开发

一、什么是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

### 实现思路 在 OpenHarmony 应用开发中使用 NAPI 实现运行系统命令的功能,需要结合 C++ 代码和 ArkTS(或 JavaScript)接口进行交互。NAPIOpenHarmony 提供的用于构建原生插件的接口集,通过它可以在 ArkTS 中调用底层 C/C++ 方法。 OpenHarmonyNAPI 接口定义在 `napi/native_api.h` 和 `napi/native_node_api.h` 头文件中,在不同版本的源码中路径有所差异: - 在 OpenHarmony 3.1 Release 中位于 `//foundation/ace/napi/interfaces/kits` 目录下; - 在 OpenHarmony 3.2 Beta3 中则分别位于 `//foundation/arkui/napi/interfaces/kits` 和 `//foundation/arkui/napi/interfaces/inner_api` 目录下[^1]。 --- ### 示例代码 #### 1. 创建 NAPI 插件结构 按照 OpenHarmonyNAPI 工具生成方式,可使用工具生成基础文件结构,包括: - `binding.gyp` - `BUILD.gn` - `*.cpp` 和 `.h` 源文件 - 构建脚本等[^2] #### 2. 编写 C++ 代码执行命令 以下是一个基于 OpenHarmonyNAPI 插件示例,实现执行系统命令并返回输出结果: ```cpp #include "napi/native_api.h" #include "napi/native_node_api.h" #include <cstdlib> #include <string> static std::string ExecuteCommand(const std::string& command) { char buffer[128]; std::string result; FILE* pipe = popen(command.c_str(), "r"); if (!pipe) return "ERROR: popen failed"; while (fgets(buffer, sizeof(buffer), pipe)) { result += buffer; } pclose(pipe); return result; } static napi_value RunCommand(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value args[1]; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); napi_valuetype type; napi_typeof(env, args[0], &type); if (type != napi_string) { napi_throw_type_error(env, nullptr, "Argument must be a string"); return nullptr; } size_t length; napi_get_value_string_utf8(env, args[0], nullptr, 0, &length); char* command = new char[length + 1]; napi_get_value_string_utf8(env, args[0], command, length + 1, &length); std::string output = ExecuteCommand(command); delete[] command; napi_value result; napi_create_string_utf8(env, output.c_str(), output.length(), &result); return result; } EXTERN_C_START static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc = {"runCommand", nullptr, RunCommand, nullptr, nullptr, nullptr, napi_default, nullptr}; napi_define_properties(env, exports, 1, &desc); return exports; } EXTERN_C_END extern "C" { OHOS_EXPORT napi_value OHOS_MODULE(run_command, Init); } ``` #### 3. 配置编译文件 创建 `binding.gyp` 文件以支持 `node-gyp` 编译流程(适用于兼容 Node.js 样式),或者使用 `BUILD.gn` 进行 OpenHarmony 原生构建配置[^2]。 ```json { "targets": [ { "target_name": "run_command", "sources": ["src/run_command.cpp"], "include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"], "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"] } ] } ``` #### 4. 在 ArkTS 中调用插件 将编译后的 `.so` 文件打包为 `.har` 包,并在 ArkTS 中引用: ```typescript import { runCommand } from 'library' @Entry @Component struct Index { @State message: string = 'Hello World' build() { Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) Button('Run Command') .onClick(() => { this.message = "Result: " + runCommand("ls -la"); }) } .width('100%') } .height('100%') } } ``` --- ### 注意事项 - 确保目标设备上具有执行命令的权限。 - 若涉及敏感操作(如执行 shell 命令),需遵循 OpenHarmony 安全机制要求。 - 使用 `popen` 可能存在安全隐患,建议对输入参数做严格校验。 - 插件应经过充分测试后再集成到应用中,确保稳定性与安全性[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值