Napi入门(一)—如何创建一个NAPI 工程

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

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

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

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

NAPI是什么?

NAPI(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架。

在移动应用开发中需要使用 C/C++ 实现的场景有很多,比如音视频处理,图像处理等较高性能要求的场景。

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

创建NAPI 工程

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

#创作者激励#【坚果派-坚果】Napi入门【一】-开源基础软件社区

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

#创作者激励#【坚果派-坚果】Napi入门【一】-开源基础软件社区

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

#创作者激励#【坚果派-坚果】Napi入门【一】-开源基础软件社区

我么可以点开来看一下。

#创作者激励#【坚果派-坚果】Napi入门【一】-开源基础软件社区

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

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

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

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

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

#创作者激励#【坚果派-坚果】Napi入门【一】-开源基础软件社区

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

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

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 文件,它的源码如下所示:

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

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

package.json解读

在 cpp 的 libentry 目录下生成了 package.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 内容如下所示:

# 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 文件,源码如下所示:

#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 的代码不是很复杂,我们可以做如下拆分:

  • 引入头文件
#include "napi/native_api.h"
#include <js_native_api.h>
#include <js_native_api_types.h>

引入头文件,作用和 TS 里的 import 类似,不再详述。

  • 注册napi模块
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 模块注册到系统中。

  • 方法定义
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() 方法。

  • 方法实现
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 类型并返回。

  • 模块导入
import testNapi from 'libentry.so'

根据前边的编译配置,cpp 目录下的源码最终打包成了 libentry.so,使用前直接引入即可。

  • 方法调用
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)​​官网说明如下:

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

#创作者激励#【坚果派-坚果】Napi入门【一】-开源基础软件社区

C/C++转napi_value

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

int类型转换

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类型转换

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类型转换

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类型转换

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类型转换

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类型转换

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类型转换

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/pages/v3.2Beta/zh-cn/application-dev/reference/native-lib/third_party_napi/napi.md/。

原生模块扩展开发框架:

https://gitee.com/openharmony/arkui_napi。

Node_API :用于封装JavaScript能力为native插件的API,独立于底层JavaScript,并作为Node.js的一部分。

​Native API中支持的标准库​​ :目前支持标准C库、C++库、OpenSL ES、zlib。

C常用函数库 :math.h。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/sin。

Cmake :管理源代码构建的工具。

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

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

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

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

2023-03-10 09:41:16

NAPI框架鸿蒙

2022-10-25 15:05:17

NAPI开发鸿蒙

2023-03-14 21:29:26

MysubsysNAPI框架

2022-10-11 15:04:28

NAPI开发鸿蒙

2022-10-09 15:05:50

NAPI框架鸿蒙

2023-04-26 15:29:35

NAPI模块鸿蒙

2018-03-23 10:00:34

PythonTensorFlow神经网络

2015-03-24 19:48:24

2023-06-12 08:00:48

Napi-rsRust 前端工具

2010-08-05 15:46:13

Flex行为Flex效果

2013-05-02 10:40:24

xcode

2021-09-16 15:08:08

鸿蒙HarmonyOS应用

2024-01-03 15:41:49

2017-02-10 20:00:17

Linux共享目录命令

2020-06-02 10:04:58

IT部门首席信息官CIO

2021-12-08 15:07:51

鸿蒙HarmonyOS应用

2021-05-06 10:33:30

C++Napiv8

2016-03-08 09:52:22

xcode插件开发

2019-08-12 09:55:10

GitHub项目终端

2010-07-30 14:50:38

Flex项目
点赞
收藏

51CTO技术栈公众号