0
点赞
收藏
分享

微信扫一扫

#夏日挑战赛#OHOS Native开发

​​本文正在参加星光计划3.0–夏日挑战赛​​

摘要: 本文主要讲述Node.js中的N-API模块,并以一个简单的例子展示如何实现JS->N-API->C/C++调用过程。

技术选型:

UI开发选择C位的eTS,逻辑实现选择C/C++,API调用选择NAPI。OpenHarmonyOS JS运行时与native框架之间的交互实现方式有三种,分别是:JSI机制、JNI机制、NAPI机制。​​OpenHarmony 源码解析之JavaScript API框架(NAPI)​​ 今天咱们就来探探eTS+napi的实现。 eTS开发介绍: ​​TS ArkUI-基于TS扩展的声明式开发范式-方舟开发框架(ArkUI)-UI-开发-HarmonyOS应用开发​​

napi开发介绍: ​​Native API在应用工程中的使用指导​​

案例: ​​HarmonyOS系统Codelabs技术指导_华为HarmonyOS源代码 - HarmonyOS应用开发官网​​

新建项目

1.打开DevEco Studio,点击 Create Project创建一个新工程,如果你之前已经创建过工程,则点击File -> New -> New project

2.进入选择ability template界面,选择Native C++。

#夏日挑战赛#OHOS Native开发_javascript

3.进入配置工程界面,选择默认,然后点击Finish。

#夏日挑战赛#OHOS Native开发_c++_02

代码结构解读

本节只对核心代码进行讲解,整个工程的代码结构如下:

#夏日挑战赛#OHOS Native开发_鸿蒙_03

文件说明如下:

├── cpp:                                               // C++代码区
│ ├── types: // 接口存放文件夹
│ │ └── libentry
│ │ ├── index.d.ts // 接口文件,供ets调用
│ │ └── package.json // 接口注册配置文件
│ ├── CmakeLists.txt // Cmake编译配置文件
│ └── hello.cpp // C++源代码
└── ets // ets代码区
└── MainAbility
├── pages
| └──index.ets // 主页面
└── app.ets // Ability,提供对Ability生命周期、上下文环境等调用管理

eTS调用C++方法流程

index.ets中引入动态库,调用动态库中的add方法。

import testNapi from "libentry.so"  
...
Text(this.message)
...
.onClick(() => { console.log("Test NAPI 2 + 3 = " + testNapi.add(2, 3)); }

eTS与C/C++的衔接处就是N-API。


N-API是什么

N-API是一个C风格的API,使用C/C++语言编写,是Node.js提供的一套用于开发Native(C/C++)模块的接口。N-API是Node.js 8.0.0新增的实验性特性,作为Node.js本身的一部分被引入,在Node.js 10.0.0正式启用。


遇见N-API

接下来将使用上面生成的demo作为示例介绍N-API。首先N-API的调用顺序为:模块注册->属性定义->函数实现。


函数实现

我们需要定义​​Add​​方法,并返回求和的结果。文件的开头我们需要引入​​node_api.h​​头文件。然后我们需要实现一个回调函数,其定义为:

typedef napi_value (*napi_callback)(napi_env env, napi_callback_info info);

​napi_callback​​是一个函数指针,其接受类型分别为​​napi_env​​以及​​napi_callback_info​​的两个参数,并返回类型为​​napi_value​​的值。​​Add​​方法中涉及到的几个类型定义及其用途如下:

  • ​napi_value​​类型是一个用于表示Javascript对象的指针
  • ​napi_env​​类型用于存储Javascript虚拟机的上下文
  • ​napi_callback_info​​类型传递调用时的上下文信息

我们定义的​​Add​​方法如下:

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); // 从info中获取传入的参数并依次放入参数数组中

napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0); // 对第一个js参数类型进行判定

napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1); // 对第二个js参数类型进行判定

double value0;
napi_get_value_double(env, args[0], &value0); // 将第一个传入参数转化为double类型

double value1;
napi_get_value_double(env, args[1], &value1); // 将第二个传入参数转化为double类型

napi_value sum;
napi_create_double(env, value0 + value1, &sum); // 对传入参数进行计算

return sum;
}

在上面的实现中可以看到参数由JS类型转化为C/C++类型,求和后结果又由C/C++类型转化为JS类型。N-API提供Javascript与Native(C/C++)数据类型一对一相互转换功能,具体实现在 /foundation/ace/napi/native_engine/native_api.cppnative_api.cpp。

另外上面调用的函数都返回一个​​napi_status​​​类型的值,当其值为​​napi_ok​​​时代表函数执行成功。​​napi_status​​​是一个用于指示N-API中状态的枚举类型,其值可参考​​napi_status​​。


属性定义

方法实现后我们需要实现模块初始化函数。模块初始化函数需要满足下列函数定义:

typedef napi_value (*napi_addon_register_func)(napi_env env, napi_value exports);

在模块的初始化中,我们定义模块需要暴露的方法及属性。函数如下所示:

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

在我们的的初始化函数中,需要在模块的​​exports​​对象中定义方法​​Add​​的属性。在定义属性之前,我们需要创建一个​​napi_property_descriptor​​类型的属性描述符,该类型的定义如下:

typedef struct {
const char* utf8name;
napi_value name;

napi_callback method;
napi_callback getter;
napi_callback setter;
napi_value value;

napi_property_attributes attributes;
void* data;
} napi_property_descriptor;

对于本文示例中需要使用的属性值描述如下所示,关于​​napi_property_descriptor​​的更多描述可参考​​napi_property_descriptor​​。

  • ​utf8name​​:UTF-8编码的字符序列
  • ​name​​:由Javascript对象表示的字符串或者Symbol utf8name以及name只能二选一,其代表属性的名称。
  • ​method​​:将该属性设置为一个返回值为Javascript对象的方法(回调函数)
  • ​attributes​​:属性的行为控制标志,示例中使用了默认的napi_default值,更多描述可参考napi_property_attributes

我们这里主要设置了​​utf8name​​以及​​method​​属性。

在创建属性描述符后,便需要将其在模块的​​exports​​对象中定义,使Javascript代码能够访问。对象属性的定义使用了​​napi_define_properties​​函数,它可以快速的为一个对象定义指定数量的属性。该函数定义为:

napi_status napi_define_properties(napi_env env,
napi_value object,
size_t property_count,
const napi_property_descriptor *properties
);

  • ​env​​:JSVM环境上下文
  • ​object​​:需要定义属性的Javascript对象
  • ​property_count​​:属性数量
  • ​properties​​:属性描述符数组

同样,​​napi_define_properties​​​也返回了一个​​napi_status​​类型的值表示函数调用成功与否。


模块注册

最后,我们只需注册一下模块就可以了。

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

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

到此为止,我们的​​hello​​模块便编写完成了。


结束语

对于开发Node.js Native扩展模块的小伙伴,官方提供了一个自动化工具,让底层开发者只关注底层业务逻辑即可:​​NAPI框架生成工具​​

举报

相关推荐

0 条评论