OpenHarmony开发者论坛

标题: NAPI入门 [打印本页]

作者: 润开鸿_坚果    时间: 2023-11-22 13:02
标题: NAPI入门
[md]
## NAPI是什么?

NAPI(Native API)组件是一套对外接口基于Node.js [N-API规范开发的原生模块扩展开发框架](https://gitee.com/openharmony/arkui_napi)。

在移动应用开发中需要使用 `C/C++` 实现的场景有很多,[比如音视频处理,图像处理等较高性能要求的场景](https://gitee.com/openharmony/co ... /NativeTemplateDemo)。

OpenHarmony 提供了 NAPI 框架用于实现 JS 和 C/C++ 互相调用的能力,DevEco Studio 默认支持创建 NAPI 应用,我们今天就来创建一个NAPI 工程。

## 创建NAPI 工程

打开IDE,选择创建项目,选择下面的Native C++模板

![image-20230301145443732](https://luckly007.oss-cn-beijing ... 230301145443732.png)

点击 **Next** 按钮后,项目名称选择NapiHello,点击finish即可

![image-20230301145408992](https://luckly007.oss-cn-beijing ... 230301145408992.png)

等待项目加载完成,如果大家观察细致的话,会发现,这里面有个不同之处就是多了CPP目录

![image-20230301150512194](https://luckly007.oss-cn-beijing ... 230301150512194.png)

我么可以点开来看一下

![image-20230301151625961](https://luckly007.oss-cn-beijing ... 230301151625961.png)

该目录用来存放 cpp 的源码及相关配置文件,各文件说明如下:

- **hello.cpp**:**index.d.ts** 文件中声明的方法的 C++ 实现源码。
- **CMakeLists.txt**:是cmake用来生成Makefile文件需要的一个描述编译链接的脚本文件。
- **index.d.ts**:对 ts 提供的方法声明。
- **package.json**:打包的配置文件。

另外 **CMakeLists.txt** 文件还会在 **build-profile.json5** 里做配置,代码如下所示:

```json
{
  "apiType": 'stageMode',
  "buildOption": {
    "externalNativeOptions": {
      "path": "./src/main/cpp/CMakeLists.txt",
      "arguments": "",
      "cppFlags": "",
    }
  },
  "targets": [
    {
      "name": "default"
    },
    {
      "name": "ohosTest",
    }
  ]
}
```

点击自动化签名,然后运行项目就可以

![image-20230301154513142](https://luckly007.oss-cn-beijing ... 230301154513142.png)

这个时候我们就可以来看一下代码内容,分析一下了

我们再来看一下index.ets的内容

```js
import hilog from '@ohos.hilog';
import testNapi from 'libentry.so'

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

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.message = "值为"+testNapi.add(2, 3)
            hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));


          })
      }
      .width('100%')
    }
    .height('100%')
  }
}
```

运行之后,我们就可以看到

## NAPI项目简述

### index.d.ts解读

在 cpp 的 **libentry** 目录下生成了 **index.d.ts** 文件,它的源码如下所示:

```typescript
export const add: (a: number, b: number) => number;
```

**export const** 表示导出一个常量以便在其它文件中使用。**add** 是一个返回类型为 `number` 的方法,它的参数类为 `number` 类型。

### package.json解读

在 cpp 的 libentry 目录下生成了 **package.json** 文件,该文件是打包的配置文件,内容如下所示:

```json
{
  "name": "libentry.so",
  "types": "./index.d.ts"
}
```

设置 **libentry.so** 库和 **index.d.ts** 相关联,便于在 TS 文件中引入 **libentry.so** 时调用库中的相关方法。

### CMakeLists.txt解读

CMake 是一个开源跨平台的构建工具,旨在构建、测试和打包软件,CMake 是 makefile 的上层工具,用于跨平台构建环境,生成可移植的 makefile 并简化自动动手写 makefile 的工作量,在 cpp 目录下默认生成的 CMakeLists.txt 内容如下所示:

```cmake
# the minimum version of CMake.
# 声明使用 CMAKE 的最小版本号
cmake_minimum_required(VERSION 3.4.1)

# 声明项目的名称
project(oh_0400_napi)

# set命令,格式为set(key value),表示设置key的值为value,其中value可以是路径,也可以是许多文件。
# 本例中设置NATIVERENDER_ROOT_PATH的值为${CMAKE_CURRENT_SOURCE_DIR}
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# 添加项目编译所需要的头文件的目录
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

# 生成目标库文件libentry.so,entry表示最终的库名称,SHARED表示生成的是动态链接库,
# hello.cpp表示最终生成的libentry.so中所包含的源码
# 如果要生成静态链接库,把SHARED该成STATIC即可
add_library(entry SHARED hello.cpp)

# 把libentry.so链接到libace_napi.z.so上
target_link_libraries(entry PUBLIC libace_napi.z.so)
```

### hello.cpp解读

在 cpp 目录下默认生成的 **hello.cpp** 文件,源码如下所示:

```c
#include "napi/native_api.h"
#include <js_native_api.h>
#include <js_native_api_types.h>

static napi_value Add(napi_env env, napi_callback_info info)
{
    size_t requireArgc = 2;
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);

    return sum;

}


EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
    };

    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version =1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}
```

**hello.cpp** 的代码不是很复杂,我们可以做如下拆分:

- **引入头文件**
  
  ```c
  #include "napi/native_api.h"
  #include <js_native_api.h>
  #include <js_native_api_types.h>
  ```
  
  引入头文件,作用和 TS 里的 **import** 类似,不再详述。
- **注册napi模块**
  
  ```c
  static napi_module demoModule = {
      .nm_version =1,//nm_version:nm版本号,默认值为 1。
      .nm_flags = 0,//nm标记符,默认值为 0。
      .nm_filename = nullptr,//暂不关注,使用默认值即可。
      .nm_register_func = Init,//指定nm的入口函数。
      .nm_modname = "entry",//指定 TS 页面导入的模块名,例如:`import testNapi from 'libentry.so'` 中的 testNapi
    //就是当前的nm_modname。
      .nm_priv = ((void*)0),//暂不关注,使用默认值即可。
      .reserved = { 0 },//暂不关注,使用默认值即可。
  };
  
  extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
  {
      napi_module_register(&demoModule);
  }
  ```
  
  定义 NAPI 模块,类型为 **napi_module** 结构体,各字段说明如下:
  
  - **nm_version**:nm版本号,默认值为 1。
  - **nm_flags**:nm标记符,默认值为 0。
  - **nm_filename**:暂不关注,使用默认值即可。
  - **nm_register_func**:指定nm的入口函数。
  - **nm_modname**:指定 TS 页面导入的模块名,例如:`import testNapi from 'libentry.so'` 中的 testNapi 就是当前的nm_modname。
  - **nm_priv**:暂不关注,使用默认值即可。
  - **reserved**:暂不关注,使用默认值即可。
  
  `extern "C"` 简单理解就是告诉编译器这部分代码按照 C 语言进行编译而不是 C++ 语言编译。`__attribute__((constructor))` 声明方法的执行时机,它表示 `RegisterEntryModule()` 方法在 `main()` 方法执行前执行, `RegisterEntryModule()` 方法内调用了 `napi_module_register()` 方法,该方法是 NAPI 提供的模块注册方法,表示把定义的 **demoModule** 模块注册到系统中。
- **方法定义**
  
  ```c
  EXTERN_C_START
  static napi_value Init(napi_env env, napi_value exports)
  {
      napi_property_descriptor desc[] = {
          { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
      };
  
      napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
      return exports;
  }
  EXTERN_C_END
  ```
  
  `Init()` 方法内声明了 **napi_property_descriptor** 结构体,结构体的定义看第一个和第三个参数即可,第一个参数 **add** 表示应用层 JS 声明的方法,**Add** 表示 Native C++ 实现的方法,然后调用 NAPI 的 **napi_define_properties()** 方法将 `add` 和 `Add` 做个映射,最后通过 **exports** 变量对外导出,实现 JS 端调用 **add** 方法时进而调用到 C++ 的 **Add()** 方法。
- **方法实现**
  
  ```c
  static napi_value Add(napi_env env, napi_callback_info info)
  {
      // 获取 2 个参数,napi_value是对 JS 类型的封装
      size_t requireArgc = 2;
      size_t argc = 2;
      napi_value args[2] = {nullptr};
      // 调用napi_get_cb_info方法,从 info 中读取传递进来的参数放入args里
      napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
  
      // 获取参数并校验类型
      napi_valuetype valuetype0;
      napi_typeof(env, args[0], &valuetype0);
      napi_valuetype valuetype1;
      napi_typeof(env, args[1], &valuetype1);
  
      // 调用napi_get_value_double把 napi_value 类型转换成 C++ 的 double 类型
      double value0;
      napi_get_value_double(env, args[0], &value0);
      double value1;
      napi_get_value_double(env, args[1], &value1);
  
      // 调用napi_create_double方法把 C++类型转换成 napi_value 类型
      napi_value sum;
      napi_create_double(env, value0 + value1, &sum);
  
      // 返回 napi_value 类型
      return sum;
  
  }
  ```
  
  `Add()` 方法注释的很清楚,首先从 **napi_callback_info** 中读取 napi_value 类型的参数放入到 **args** 中,然后从 **args** 中读取参数并把 napi_value 类型转换成 C++ 类型后进行加操作,最后把相加的结果转换成 napi_value 类型并返回。
- **模块导入**
  
  ```typescript
  import testNapi from 'libentry.so'
  ```
  
  根据前边的编译配置,cpp 目录下的源码最终打包成了 libentry.so,使用前直接引入即可。
- **方法调用**
  
  ```typescript
  import hilog from '@ohos.hilog';
  import testNapi from 'libentry.so'
  
  @Entry
  @Component
  struct Index {
    @State message: string = 'Hello World'
  
    build() {
      Row() {
        Column() {
          Text(this.message)
            .fontSize(50)
            .fontWeight(FontWeight.Bold)
            .onClick(() => {
              this.message = "值为"+testNapi.add(2, 3)
              hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
  
  
            })
        }
        .width('100%')
      }
      .height('100%')
    }
  }
  ```
  
  引入 **libentry.so** 模块后,就可以直接调用 `add()` 方法了。

## NAPI数据类型

## napi_value数据类型

OpenHarmony NAPI 将 ECMAScript 标准中定义的 Boolean、Null、Undefined、Number、BigInt、String、Symbol和 Object 这八种数据类型以及函数对应的 Function 类型统一封装成了 napi_value 类型,它是 JS 数据类型和 C/C++ 数据类型之间的桥梁,[napi_value (opens new window)](https://nodejs.org/api/n-api.html#napi_value)官网说明如下

napi_value 表示 JS 值的不透明指针,在 C/C++ 端要使用 JS 端传递的数据类型,都是通过 NAPI 提供的相关方法把napi_value转换成 C/C++ 类型后再使用,同理当需要把 C/C++的数据传递给 JS 应用层也要通过 NAPI 提供的方法把 C/C++ 端的数据转换成 napi_value 再向上传递。

![image-20230302211659632](https://luckly007.oss-cn-beijing ... 230302211659632.png)

## C/C++转napi_value

NAPI提供了 **napi_create_** 开头的方法表示把 C/C++ 类型转换成 **napi_value** 类型,常见方法如下所示:

### int类型转换

```c
NAPI_EXTERN napi_status napi_create_int32(napi_env env,
                                          int32_t value,
                                          napi_value* result);
NAPI_EXTERN napi_status napi_create_uint32(napi_env env,
                                           uint32_t value,
                                           napi_value* result);
NAPI_EXTERN napi_status napi_create_int64(napi_env env,
                                          int64_t value,
                                          napi_value* result);
```

把 C/C++ 的 **int32_t**、**uint32_t** 以及 **int64_t** 类型转换成 **napi_value** 类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **value**:C/C++端的 int 类型的值。
- **result**:napi_value,返回给 JS 应用层的数据。

### double类型转换

```c
NAPI_EXTERN napi_status napi_create_double(napi_env env,
                                           double value,
                                           napi_value* result);
```

把 C/C++ 端的 **double** 类型转换成 **napi_value** 类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **value**:C/C++ 端的 double 类型的值。
- **result**:napi_value,返回给 JS 应用层的数据。

### string类型转换

```c
NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env,
                                                  const char* str,
                                                  size_t length,
                                                  napi_value* result);
NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env,
                                                const char* str,
                                                size_t length,
                                                napi_value* result);
NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env,
                                                 const char16_t* str,
                                                 size_t length,
                                                 napi_value* result);
```

把 C/C++ 端的 **char** 类型转换成 **napi_value** 类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **str**:C/C++端的字符串类型的值。
- **size_t**:str 的长度。
- **result**:napi_value,返回给 JS 应用层的数据。

## napi_value转C/C++

NAPI提供了 **napi_get_value_** 开头的方法表示把 **napi_value** 转换成 C/C++ 类型,常见方法如下所示:

### int类型转换

```c
NAPI_EXTERN napi_status napi_get_value_int32(napi_env env,
                                             napi_value value,
                                             int32_t* result);
NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env,
                                              napi_value value,
                                              uint32_t* result);
NAPI_EXTERN napi_status napi_get_value_int64(napi_env env,
                                             napi_value value,
                                             int64_t* result);
```

把 JS 端的 **number** 类型转换成 C/C++ 的对应数据类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **value**:JS 端传递进来的数据。
- **result**:接收 value 的值。

### double类型转换

```c
NAPI_EXTERN napi_status napi_get_value_double(napi_env env,
                                              napi_value value,
                                              double* result);
```

把 JS 端的 **number** 类型转换成 C/C++ 的 **double** 类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **value**:JS 端传递进来的数据。
- **result**:接收 value 的值。

### string类型转换

```c
NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env,
                                                     napi_value value,
                                                     char* buf,
                                                     size_t bufsize,
                                                     size_t* result);

// Copies UTF-8 encoded bytes from a string into a buffer.
NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env,
                                                   napi_value value,
                                                   char* buf,
                                                   size_t bufsize,
                                                   size_t* result);

// Copies UTF-16 encoded bytes from a string into a buffer.
NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env,
                                                    napi_value value,
                                                    char16_t* buf,
                                                    size_t bufsize,
                                                    size_t* result);
```

把 JS 端的 **string** 类型转换成 C/C++ 的 **char** 类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **value**:napi_value,JS 端传递进来的数据。
- **buf**:char数组,用来存放napi_value中的 string 值
- **bufsize**:char数组长度
- **result**:接收 value 的值。

### boolean类型转换

```c
NAPI_EXTERN napi_status napi_get_value_bool(napi_env env,
                                            napi_value value,
                                            bool* result);
```

把 JS 端的 **boolean** 类型转换成 C/C++ 的 **bool** 类型,参数说明如下:

- **env**:方法调用者的运行环境,包含 JS 引擎等。
- **value**:JS 端传递进来的数据。
- **result**:接收 value 的值。

## 参考

Node-API

https://docs.openharmony.cn/page ... party_napi/napi.md/

原生模块扩展开发框架:

https://gitee.com/openharmony/arkui_napi

[Node_API](https://gitee.com/openharmony/do ... _party_napi/napi.md) :用于封装JavaScript能力为native插件的API,独立于底层JavaScript,并作为Node.js的一部分。

[Native API中支持的标准库](https://gitee.com/openharmony/do ... _party_libc/musl.md) :目前支持标准C库、C++库、OpenSL ES、zlib。

[C常用函数库](https://zh.cppreference.com/w/c/numeric/math) :math.h。

https://developer.mozilla.org/zh ... al_Objects/Math/sin

[Cmake](https://cmake.org/cmake/help/latest/) :管理源代码构建的工具。

[/md]

[/md]
作者: gd6321374    时间: 2023-12-6 17:25
你好,博主,新手小白请教一下
现在,我手上已经有一个Linux c++ 项目,在麒麟等linux 系统可以正常跑起来了。并且已经投入使用了。
目前,根据公司战略性需求,需要将项目在OpenHarmony 上运行,请问我完整代码在上面可以直接编译,预留NAPI交互接口提供给上层应用,这样子可以吗?
谢谢。
作者: 深开鸿_王石    时间: 2023-12-8 13:56
回复 gd6321374: 这个有好几种开发方式:
1,直接hap + lib方式进行开发,hap是可以走nativec++的开发方式的;
2,若原来是QT的,现在oh也支持QT,可以用QT方式;
3,纯血鸿蒙方式,那就需要你考虑 framework + service方式,framework提供native + js接口,service采用sa方式提供服务,遵守samgr管理方式




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5