#打卡不停更#三方库移植之napi开发[4]异步调用:callback&promise(代码片段)

author author     2022-12-06     273

关键词:

三方库移植之NAPI开发系列文章《Hello OpenHarmony NAPI》、《C/C++与JS的数据类型转换》其接口都是同步的。对IO、CPU密集型任务需要异步处理。 NAPI支持异步模型,提供了Promise、Callback 2种方式。


往期回顾三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI

三方库移植之NAPI开发[2]C/C++与JS的数据类型转换

三方库移植之NAPI开发[3]通过IDE开发NAPI工程

(目录)


写在开头

  • 本文在三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI的基础上修改hellonapi.cpp、index.ets,接着学习NAPI异步模型的Promise、Callback方式。
  • 本文共有三个示例,分别是Callback 异步接口示例、Promise 异步接口示例、规范异步接口示例。在本文末尾的资源中提供了这三个示例的源代码,读者可以下载在开发板上运行。
  • 开发基于最新的OpenHarmony3.2Beta3版本及API9,标准系统开发板为润和软件DAYU200。

NAPI异步方式实现原理

  • 同步方式和异步方式: 同步方式,所有的代码处理都在原生方法(主线程)中完成。 异步方式,所有的代码处理在多个线程中完成。

  • 实现NAPI异步方法的步骤: 1)立即返回一个临时结果给js调用者 2)另起线程完成异步业务逻辑的执行 3)通过callback或promise返回真正的结果

  • 异步工作项工作时序图

    • 原生方法被调用时,原生方法完成数据接收数据类型转换存入上下文数据,之后创建异步工作项
    • 异步工作项会加入调度队列,由异步工作线程池统一调度,原生方法返回空值(Callback方式)或返回Promise对象(Promise方式)。
    • 异步方式依赖NAPI框架提供的napi_create_async_work()函数创建异步工作项 napi_create_async_work()在foundation/arkui/napi/native_engine/native_node_api.cpp第71行
NAPI_EXTERN napi_status napi_create_async_work(napi_env env,
                                               napi_value async_resource,
                                               napi_value async_resource_name,
                                               napi_async_execute_callback execute,
                                               napi_async_complete_callback complete,
                                               void* data,
                                               napi_async_work* result)

参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] async_resource: 可选项,关联async_hooks。
[in] async_resource_name: 异步资源标识符,主要用于async_hooks API暴露断言诊断信息。
[in] execute: 执行业务逻辑计算函数,由worker线程池调度执行。在该函数中执行IO、CPU密集型任务,不阻塞主线程。
[in] complete: execute参数指定的函数执行完成或取消后,触发执行该函数。此函数在EventLoop线程中执行。
[in] data: 用户提供的上下文数据,用于传递数据。
[out] result: napi_async_work*指针,用于返回当前此处函数调用创建的异步工作项。 返回值:返回napi_ok表示转换成功,其他值失败。

napi_create_async_work里有两个回调:

  • execute

    • execute函数用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在worker线程中完成业务逻辑计算(不阻塞主线程)并将结果写入上下文数据。
    • 因为execute函数不在JS线程中,所以不允许execute函数调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。
  • complete

    • 业务逻辑处理execute函数执行完成或被取消后,触发EventLoop执行complete函数,complete函数从上下文数据中获取结果,转换为JS类型,调用JS回调函数通过Promise resolve()返回结果。
    • 可以调用napi的接口,将execute中的返回值封装成JS对象返回。此回调在JS线程中执行。
  • 管理简单的异步操作的方法还有这些

    • napi_delete_async_work(napi_env env, napi_async_work work) 删除异步工作线程
    • napi_queue_async_work(napi_env env, napi_async_work work) 将刚创建的异步工作项加到队列(排队),由底层去调度执行
    • napi_cancel_async_work(napi_env env, napi_async_work work) 取消异步工作项

NAPI支持异步模型

  • OpenHarmony标准系统异步接口实现支持Promise方式和Callback方式。NAPI支持异步模型,提供了Promise、Callback方式。

  • 标准系统异步接口实现规范要求,若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式。

    • 由应用开发者决定使用哪种方式,通过是否传递Callback函数区分异步方法是Callback方式还是Promise方式
    • 不传递Callback即为Promise方式(方法执行结果为Promise实例对象),否则为Callback方式

Callback 异步接口

Callback 异步接口示例代码

hellonapi.cpp文件

#include <string.h>
#include <stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData 
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;
  napi_ref callback = nullptr;
  double args[2] = 0;
  double result = 0;
;

// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) 
  AddonData *addonData = (AddonData *)data;

  // 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
  addonData->result = addonData->args[0] + addonData->args[1];


// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) 
  AddonData *addonData = (AddonData *)data;
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;

  // 执行回调函数
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  // 删除napi_ref对象
  if (addonData->callback != nullptr) 
    napi_delete_reference(env, addonData->callback);
  

  // 删除异步工作项
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;


static napi_value addCallback(napi_env env, napi_callback_info info) 
  // 获取3个参数,值的类型是js类型(napi_value)
  size_t argc = 3;
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // 获取并判断js参数类型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) 
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  

  napi_valuetype valuetype2;
  NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
  if (valuetype2 != napi_function) 
    napi_throw_type_error(env, nullptr, "Callback function expected.");
    return NULL;
  

  // 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
  auto addonData = new AddonData
      .asyncWork = nullptr,
  ;

  // 将接收到的参数传入用户自定义上下文数据
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
  NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));

  // 创建async work,创建成功后通过最后一个参数接收async work的handle
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
  napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,
                         &addonData->asyncWork);

  // 将刚创建的async work加到队列,由底层去调度执行
  napi_queue_async_work(env, addonData->asyncWork);

  // 原生方法返回空对象
  napi_value result = 0;
  NAPI_CALL(env, napi_get_null(env, &result));
  return result;


// napi_addon_register_func
static napi_value registerFunc(napi_env env, napi_value exports) 
  static napi_property_descriptor desc[] = 
      DECLARE_NAPI_FUNCTION("addCallback", addCallback),
  ;
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;


// 定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from @ohos.ohos_shared_library_name
//示例对应eTS代码为:import hellonapi from @ohos.hellonapi
static napi_module hellonapiModule = 
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模块对外接口注册函数
    .nm_modname = "hellonapi",  // 自定义模块名
    .nm_priv = ((void*)0),
    .reserved =  0 ,
;

// 模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()

    napi_module_register(&hellonapiModule);

index.ets

import prompt from @system.prompt;
import hellonapi from @ohos.hellonapi

@Entry
@Component
struct TestAdd 
  build() 
    Flex( direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center ) 
      Button("hellonapi.addCallback(x, y, callback)").margin(10).fontSize(20).onClick(() => 
        let num1 = 123, num2 = 456
        hellonapi.addCallback(num1, num2, (result) => 
          prompt.showToast( message: `hellonapi.addCallback($num1, $num2) = $result` )
        )
      )
    
    .width(100%)
    .height(100%)
  

@ohos.hellonapi.d.ts

declare namespace hellonapi 

	function addCallback(num1: number, num2: number, callback:(result: number) => void): void;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */


export default hellonapi;

主线程:初始化上下文数据

初始化上下文数据在异步工作项工作时序图中位置,在图中用红框标记如下

  • 异步方法需要在不同线程中传递各种业务数据上下文数据),就需要定义一个结构体保存这些被传递的信息。用于在主线程方法、Work线程、EventLoop线程之间传递数据。
  • 本示例定义的上下文数据包含:异步工作项对象回调函数2个参数(加数、被加数)、业务逻辑处理结果等4个属性。
// 定义异步工作项上下文数据
// 用户提供的上下文数据,用于在主线程方法、Work线程、EventLoop线程之间传递数据。
struct AddonData 
  napi_async_work asyncWork = nullptr;   //异步工作对象asyncWork
  napi_ref callback = nullptr;           //回调函数callback
  double args[2] = 0;                  //2个输入参数
  double result = 0;                     //业务逻辑处理结果result(返回值)
;
  • OpenHarmony的NAPI框架将ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型Function类型,都已统一封装为napi_value类型,故可如获取数据类型的参数一样获取Function类型的参数。
  • 定义好结构体后,接着我们将接收到的3个参数(加数被加数回调函数)转换存入上下文数据完成初始化上下文数据
    • number类型的(加数被加数)转换为double直接存入。
    • Function类型的参数(回调函数)怎么处理?不能直接存入napi_value类型。
      • 因为牵涉到NAPI对象生命周期管理问题。napi_value类型引用对象的生命周期在原生方法退出后结束,后面在work线程无法获取其值。
      • NAPI提供了一种生命期限长于原生方法的对象引用类型—— napi_ref,所以调用napi_create_reference()函数将接收到的napi_value类型的回调函数参数callback转换为napi_ref类型。napi_create_reference()函数定义如下:
NAPI_EXTERN napi_status napi_create_reference(napi_env env,
                                              napi_value value,
                                              uint32_t initial_refcount,
                                              napi_ref* result);
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] value: 需要创建一个引用的napi_value对象
[in] initial_refcount: 初始化引用次数。
[out] result: 指针,指向新创建的napi_ref对象。 返回值:返回napi_ok表示转换成功,其他值失败。
  • napi_ref引用对象在原生方法退出后不自动回收,由用户管理napi_ref类型对象的生命周期。
    • 用户管理napi_ref类型对象的生命周期的方法有
      • napi_create_reference() : 将napi_value包装成napi_ref引用对象
      • napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
      • napi_delete_reference() :删除napi_ref引用对象
    • 通过napi_create_reference()方法将napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方用napi_get_reference_value()方法将napi_ref还原为napi_value,用完后再用napi_delete_reference()方法删除引用对象以便释放相关内存资源。
static napi_value addAsyncCallback(napi_env env, napi_callback_info info) 
// NAPI定义API方法时的接收参数为(napi_env, napi_callback_info)
// 其中napi_callback_info为上下文的信息。

  size_t argc = 3;   // 有3个参数(`加数`、`被加数`、`回调函数`)到上下文中
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
// NAPI提供了napi_get_cb_info()方法可从napi_callback_info中获取参数列表、this及其他数据。

  ...
  // 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
  // 创建结构体addonData用于保存各种需要在异步线程中传递的数据信息
  auto addonData = new AddonData
      .asyncWork = nullptr,
  ;

  // 将接收到的3个参数(`加数`、`被加数`、`回调函数`)传入用户自定义上下文数据
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  // NAPI_CALL()是用来调用NAPI中的API的
  // NAPI提供napi_get_value_double方法将JS类型double值转换为C++类型的double值
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));  
  NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
  //调用napi_create_reference()函数将接收到的napi_value类型的回调函数callback转换为napi_ref类型,将napi_value包装成napi_ref引用对象。
  //参数解释如下
     // env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可
     // args[2]: 引用的napi_value对象 (加数和被加数)
     // 1:初始化引用1次
     // &addonData->callback: 指向新创建的napi_ref 对象(callback) 
  ...

  • NAPI_CALL()是用来调用NAPI中的API的。

主线程:创建异步工作项

  • 第一步:在创建异步工作项前,分别声明addExecuteCB、addAsyncCompleteCB这2个函数,分别用作于napi_create_async_work(napi_env env,napi_value async_resource,napi_value async_resource_name,napi_async_execute_callback execute,napi_async_complete_callback complete,void* data,napi_async_work* result)函数的execute、complete参数。
  • 第二步:利用NAPI框架提供的napi_create_async_work()函数创建异步工作项,将addExecuteCB、addAsyncCompleteCB这2个函数存入上下文数据的asyncWork属性
  • 第三步:调用napi_queue_async_work()将异步工作项加入调度队列,由异步work线程池统一调度,原生方法返回空值退出。
// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) 


// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发。
static void addAsyncCompleteCB(napi_env env, napi_status status, void *data) 


static napi_value addAsyncCallback(napi_env env, napi_callback_info info) 
  ...
  NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));

  // 创建异步工作项(async work),创建成功后通过最后一个参数接收异步工作项(async work)的handle
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
  napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addAsyncCompleteCB, (void *)addonData, &addonData->asyncWork);

  // 将刚创建的异步工作项(async work)加到队列,由work thread调度执行
  napi_queue_async_work(env, addonData->asyncWork);

  // 原生方法返回空对象
  napi_value result = 0;
  NAPI_CALL(env, napi_get_null(env, &result));
  return result;

execute函数

创建异步工作项前,声明了addExecuteCB这个函数,用作于napi_create_async_work()函数的execute参数。

  • execute函数在异步工作项被调度后在work线程中执行

    • 不阻塞主线程(不阻塞UI界面)
    • 可执行IO、CPU密集型等任务。
  • 业务逻辑计算是一个简单的加法,并把计算结果存入上下文数据的result属性

// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) 
  AddonData *addonData = (AddonData *)data;

  // 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
  addonData->result = addonData->args[0] + addonData->args[1];

complete 函数

创建异步工作项前,声明addAsyncCompleteCB这个函数,用作于napi_create_async_work()函数的complete参数。

  • 第一步:addAsyncCompleteCB从接收到的上下文数据中获取结果,调用napi_call_function()方法执行JS回调函数返回数据给JS。
  • 第二步释放(删除)过程中创建的napi_ref引用对象、异步工作项等对象。
// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) 
  AddonData *addonData = (AddonData *)data;
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;

  // 执行回调函数
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  // 删除napi_ref对象
  if (addonData->callback != nullptr) 
    napi_delete_reference(env, addonData->callback);
  

  // 删除异步工作项
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;

NAPI框架提供了napi_call_function()函数供扩展Natvie代码(C/C++代码)调用JS函数,用于执行回调函数等场景。函数定义如下:

// Methods to work with Functions
NAPI_EXTERN napi_status napi_call_function(napi_env env,
                                           napi_value recv,
                                           napi_value func,
                                           size_t argc,
                                           const napi_value* argv,
                                           napi_value* result)
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] recv: 传给被调用的this对象。
[in] func: 被调用的函数.
[in] argc: 函数参数个数(对应函数数组的长度)。
[in] argv: 函数参数数组.
[out] result: func函数执行的返回值。 返回值:返回napi_ok表示转换成功,其他值失败。

因对象生命周期管理问题,上下文数据的callback属性的类型为napi_ref,需要调用napi_get_reference_value()函数获取其指向的napi_value对象值才调用napi_call_function()函数。 napi_get_reference_value函数定义:

// Attempts to get a referenced value. If the reference is weak,
// the value might no longer be available, in that case the call
// is still successful but the result is nullptr.
NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, 
                                                 napi_ref ref,  
                                                 napi_value* result)
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] ref: napi_ref对象
[out] result: napi_ref引用的napi_value对象。 返回值:返回napi_ok表示转换成功,其他值失败。

总结

Promise异步接口

hellonapi.cpp

#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData 
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;

  napi_ref callback = nullptr;

  double args[2] = 0;

  double result = 0;

;

// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) 

  AddonData *addonData = (AddonData *)data;

  // 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
  addonData->result = addonData->args[0] + addonData->args[1];

  // addonData->result = addonData->args[0] + addonData[1];


static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) 
  AddonData *addonData = (AddonData *)data;
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_resolve_deferred(env, addonData->deferred, result);

     // 删除napi_ref对象
     if (addonData->callback != nullptr) 
     napi_delete_reference(env, addonData->callback);
     

  // 删除异步工作项
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;
  addonData = nullptr;


static napi_value addPromise(napi_env env, napi_callback_info info) 
  // 获取2个参数,值的类型是js类型(napi_value)
  size_t argc = 2;
  napi_value args[2];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // 获取并判断js参数类型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) 
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  

  // 创建promise
  napi_value promise = nullptr;
  napi_deferred deferred = nullptr;
  NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));

  // 异步工作项上下文用户数据,传递到异步工作项的execute、complete之间传递数据
  auto addonData = new AddonData
      .asyncWork = nullptr,
      .deferred = deferred,
  ;

  // 将接收到的参数传入
  NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
  NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));

  // 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handle
  napi_value resourceName = nullptr;
  napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
  napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
                         &addonData->asyncWork);

  // 将刚创建的async work加到队列,由底层去调度执行
  napi_queue_async_work(env, addonData->asyncWork);

  // 原生方法返回promise
  return promise;



// napi_addon_register_func
//2.指定模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
static napi_value registerFunc(napi_env env, napi_value exports)

    static napi_property_descriptor desc[] = 
         "addPromise", nullptr, addPromise, nullptr, nullptr, nullptr, napi_default, nullptr 
    ;
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;   


// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from @ohos.ohos_shared_library_name
//示例对应eTS代码为:import hellonapi from @ohos.hellonapi
static napi_module hellonapiModule = 
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模块对外接口注册函数
    .nm_modname = "hellonapi",  // 自定义模块名
    .nm_priv = ((void*)0),
    .reserved =  0 ,
;

//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()

    napi_module_register(&hellonapiModule);

index.ets

import prompt from @system.prompt;
import hellonapi from @ohos.hellonapi

@Entry
@Component
struct TestAdd 
  build() 
    Flex( direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center ) 
      Button("hellonapi.addPromise(x, y).then(...)").margin(1).fontSize(20).onClick(() => 
        let num1 = 123, num2 = 456
        hellonapi.addPromise(num1, num2).then((result) => 
          prompt.showToast( message: `hellonapi.addPromise($num1, $num2) = $result` )
        )
      )
    
    .width(100%)
    .height(100%)
  

@ohos.hellonapi.d.ts

declare namespace hellonapi 

	function addPromise(num1: number, num2: number): Promise<number>;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */


export default hellonapi;

规范异步接口

hellonapi.cpp

#include <string.h>
#include<stdio.h>
#include "napi/native_node_api.h"
#include "napi/native_api.h"

struct AddonData 
  napi_async_work asyncWork = nullptr;
  napi_deferred deferred = nullptr;
  napi_ref callback = nullptr;
  double args[2] = 0;
  double result = 0;
;



// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) 
  AddonData *addonData = (AddonData *)data;

  // 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。
  addonData->result = addonData->args[0] + addonData->args[1];



// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
static void addCallbackCompleteCB(napi_env env, napi_status status, void *data) 
  AddonData *addonData = (AddonData *)data;
  napi_value callback = nullptr;
  napi_get_reference_value(env, addonData->callback, &callback);
  napi_value undefined = nullptr;
  napi_get_undefined(env, &undefined);
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_value callbackResult = nullptr;

  // 执行回调函数
  napi_call_function(env, undefined, callback, 1, &result, &callbackResult);

  // 删除napi_ref对象
  if (addonData->callback != nullptr) 
    napi_delete_reference(env, addonData->callback);
  

  // 删除异步工作项
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;


static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) 
  AddonData *addonData = (AddonData *)data;
  napi_value result = nullptr;
  napi_create_double(env, addonData->result, &result);
  napi_resolve_deferred(env, addonData->deferred, result);

  // 删除napi_ref对象
  if (addonData->callback != nullptr) 
    napi_delete_reference(env, addonData->callback);
  

  // 删除异步工作项
  napi_delete_async_work(env, addonData->asyncWork);
  delete addonData;



static napi_value addAsync(napi_env env, napi_callback_info info) 
  // 获取3个参数,值的类型是js类型(napi_value)
  size_t argc = 3;
  napi_value args[3];
  napi_value thisArg = nullptr;
  NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));

  // 获取并判断js参数类型
  napi_valuetype valuetype0;
  NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
  napi_valuetype valuetype1;
  NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
  if (valuetype0 != napi_number || valuetype1 != napi_number) 
    napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
  

  // 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
  auto addonData = new AddonData
      .asyncWork = nullptr,
  ;

  if (argc == 2) 
    // 创建promise
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
    addonData->deferred = deferred;

    // 将接收到的参数传入
    NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
    NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));

    // 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handle
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addPromise", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
                           &addonData->asyncWork);

    // 将刚创建的async work加到队列,由底层去调度执行
    napi_queue_async_work(env, addonData->asyncWork);

    // 返回promise
    return promise;
   else 
    napi_valuetype valuetype2;
    NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2));
    if (valuetype2 != napi_function) 
      napi_throw_type_error(env, nullptr, "Callback function expected.");
      return NULL;
    

    // 将接收到的参数传入用户自定义上下文数据
    NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));
    NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));
    NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));

    // 创建async work,创建成功后通过最后一个参数接收async work的handle
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,
                           &addonData->asyncWork);

    // 将刚创建的async work加到队列,由底层去调度执行
    napi_queue_async_work(env, addonData->asyncWork);

    // 原生方法返回空对象
    napi_value result = 0;
    NAPI_CALL(env, napi_get_null(env, &result));
    return result;
  


// napi_addon_register_func
static napi_value registerFunc(napi_env env, napi_value exports) 
  static napi_property_descriptor desc[] = 

      DECLARE_NAPI_FUNCTION("addAsync", addAsync),
  ;
  NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
  return exports;


// 1.先定义napi_module,指定当前NAPI模块对应的模块名
//以及模块注册对外接口的处理函数,具体扩展的接口在该函数中声明
// nm_modname: 模块名称,对应eTS代码为import nm_modname from @ohos.ohos_shared_library_name
//示例对应eTS代码为:import hellonapi from @ohos.hellonapi
static napi_module hellonapiModule = 
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = registerFunc, // 模块对外接口注册函数
    .nm_modname = "hellonapi",  // 自定义模块名
    .nm_priv = ((void*)0),
    .reserved =  0 ,
;

//3.模块定义好后,调用NAPI提供的模块注册函数napi_module_register(napi_module* mod)函数注册到系统中。
// register module,设备启动时自动调用此constructor函数,把模块定义的模块注册到系统中
extern "C" __attribute__((constructor)) void hellonapiModuleRegister()

    napi_module_register(&hellonapiModule);

index.ets

import prompt from @system.prompt;
import hellonapi from @ohos.hellonapi

@Entry
@Component
struct TestAdd 
  build() 
    Flex( direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center ) 


      Button("hellonapi.addAsync(x, y, callback)").margin(10).fontSize(20).onClick(() => 
        let num1 = 123, num2 = 456
        hellonapi.addAsync(num1, num2, (result) => 
          prompt.showToast( message: `hellonapi.addAsync($num1, $num2) = $result` )
        )
      )

      Button("hellonapi.addAsync(x, y).then(...)").margin(10).fontSize(20).onClick(() => 
        let num1 = 123, num2 = 456
        hellonapi.addAsync(num1, num2).then((result) => 
          prompt.showToast( message: `hellonapi.addAsync($num1, $num2) = $result` )
        )
      )
    
    .width(100%)
    .height(100%)
  

@ohos.hellonapi.d.ts

declare namespace hellonapi 

	function addAsync(num1: number, num2: number, callback:(result: number) => void): void;
	function addAsync(num1: number, num2: number): Promise<number>;
    /**
     * 
     *
     * @since 9
     * @syscap SystemCapability.Ability.AbilityRuntime.AbilityCore
     */


export default hellonapi;

有没有老师知道原因的,交流学习学习

异步方法和同步方法.ts接口文件的比较

function add(num1: number, num2: number): number;//同步方法
function addCallback(num1: number, num2: number, callback:(result: number) => void): void;//异步callback方式
function addPromise(num1: number, num2: number): Promise<number>;//异步Promise方式	

NAPI中的数据类型

  • NAPI使用的数据类型和Node.js N-API保持一致。OpenHarmony的NAPI(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架。 通过查看foundation/arkui/napi/interfaces/inner_api/napi/native_node_api.h(编写NAPI拓展模块hellonapi.cpp需要包含的头文件)可以知道OpenHarmony基本的NAPI数据类型。 #include <js_native_api.h>中的js_native_api.h在ohos3.2beta3版本源码目录下路径为prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h。 然后再分析prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h和third_party/node/src/js_native_api_types.h内容的差别。 两者内容一致,可以推测OpenHarmony中基本的NAPI数据类型和Node.js N-API中的保持一致。而接口名方面,napi提供的接口名与三方Node.js一致,目前支持部分接口,详情见libnapi.ndk.json文件
// JSVM API types are all opaque pointers for ABI stability
// typedef undefined structs instead of void* for compile time type safety
typedef struct napi_env__* napi_env;
typedef struct napi_value__* napi_value;
typedef struct napi_ref__* napi_ref;
typedef struct napi_handle_scope__* napi_handle_scope;
typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope;
typedef struct napi_callback_info__* napi_callback_info;
typedef struct napi_deferred__* napi_deferred;
  • 以typedef struct napi_env__* napi_env为例,搜遍Node.js的源码都找不到napi_value__定义,那这个定义是什么意思呢?c语言中,允许定义一个没有定义的结构体的指针。所以napi_value其实就是一个一级指针。他不需要类型信息。
  • 在callback回调方式的处理流程中,用到了这3个与napi_ref相关的方法:

napi_create_reference() : 将napi_value包装成napi_ref引用对象 napi_get_reference_value() : 从napi_ref引用对象中取得napi_value napi_delete_reference() :删除napi_ref引用对象 当我们需要跨作用域传递napi_value时,往往需要用到上面这组方法把napi_value变成napi_ref。这是因为napi_value本质上只是一个指针,指向某种类型的napi数据对象。NAPI框架希望通过这种方式为开发者屏蔽各种napi数据对象的类型细节,类似于void* ptr的作用 。既然是指针,使用时就需要考虑它指向的对象的生命周期。

在我们的例子中,我们通过GetVisitCountAsync()方法的入参得到了js应用传递给C++的 callback function,存放在napi_value argv[1]中。但我们不能在complete_callback()方法中直接通过这个argv[1]去回调callback function(通过data对象传递也不行)。这时因为当代码执行到complete_callback()方法时,原先的主方法GetVisitCountAsync()早已执行结束, napi_value argv[1]指向的内存可能已经被释放另作他用了。

NAPI框架给出的解决方案是让开发者通过napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方再将napi_ref还原为napi_value,用完后再删除引用对象以便释放相关内存资源。

http://nodejs.cn/api/n-api.html https://nodejs.org/api/n-api.html

prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h:22:typedef struct napi_deferred__* napi_deferred;
third_party/node/src/js_native_api_types.h:22:typedef struct napi_deferred__* napi_deferred;
third_party/ejdb/src/bindings/ejdb2_node/js_native_api_types.h:22:typedef struct napi_deferred__* napi_deferred;

参考文章

https://ost.51cto.com/posts/14691

关于NAPI标准库中导出的符号列表

  • NAPI它基于Node.js N-API规范开发,因此可参考Node.js N-API了解NAPI标准库中符号列表。本文以3.2beta3源码中的node三方库为例,从third_party/node/README.OpenSource中可得知3.2beta3移植的node版本为14.19.1,因此可参考的Node.js N-API链接为14.19.1版本,如下:https://nodejs.org/docs/latest-v14.x/api/n-api.html

  • 标准库中导出的符号列表

符号类型 符号名 备注
FUNC napi_module_register
FUNC napi_get_last_error_info
FUNC napi_throw
FUNC napi_throw_error
FUNC napi_throw_type_error
FUNC napi_throw_range_error
FUNC napi_is_error
FUNC napi_create_error
FUNC napi_create_type_error
FUNC napi_create_range_error
FUNC napi_get_and_clear_last_exception
FUNC napi_is_exception_pending
FUNC napi_fatal_error
FUNC napi_open_handle_scope
FUNC napi_close_handle_scope
FUNC napi_open_escapable_handle_scope
FUNC napi_close_escapable_handle_scope
FUNC napi_escape_handle
FUNC napi_create_reference
FUNC napi_delete_reference
FUNC napi_reference_ref
FUNC napi_reference_unref
FUNC napi_get_reference_value
FUNC napi_create_array
FUNC napi_create_array_with_length
FUNC napi_create_arraybuffer
FUNC napi_create_external
FUNC napi_create_external_arraybuffer
FUNC napi_create_object
FUNC napi_create_symbol
FUNC napi_create_typedarray
FUNC napi_create_dataview
FUNC napi_create_int32
FUNC napi_create_uint32
FUNC napi_create_int64
FUNC napi_create_double
FUNC napi_create_string_latin1
FUNC napi_create_string_utf8
FUNC napi_get_array_length
FUNC napi_get_arraybuffer_info
FUNC napi_get_prototype
FUNC napi_get_typedarray_info
FUNC napi_get_dataview_info
FUNC napi_get_value_bool
FUNC napi_get_value_double
FUNC napi_get_value_external
FUNC napi_get_value_int32
FUNC napi_get_value_int64
FUNC napi_get_value_string_latin1
FUNC napi_get_value_string_utf8
FUNC napi_get_value_uint32
FUNC napi_get_boolean
FUNC napi_get_global
FUNC napi_get_null
FUNC napi_get_undefined
FUNC napi_coerce_to_bool
FUNC napi_coerce_to_number
FUNC napi_coerce_to_object
FUNC napi_coerce_to_string
FUNC napi_typeof
FUNC napi_instanceof
FUNC napi_is_array
FUNC napi_is_arraybuffer
FUNC napi_is_typedarray
FUNC napi_is_dataview
FUNC napi_is_date
FUNC napi_strict_equals
FUNC napi_get_property_names
FUNC napi_set_property
FUNC napi_get_property
FUNC napi_has_property
FUNC napi_delete_property
FUNC napi_has_own_property
FUNC napi_set_named_property
FUNC napi_get_named_property
FUNC napi_has_named_property
FUNC napi_set_element
FUNC napi_get_element
FUNC napi_has_element
FUNC napi_delete_element
FUNC napi_define_properties
FUNC napi_call_function
FUNC napi_create_function
FUNC napi_get_cb_info
FUNC napi_get_new_target
FUNC napi_new_instance
FUNC napi_define_class
FUNC napi_wrap
FUNC napi_unwrap
FUNC napi_remove_wrap
FUNC napi_create_async_work
FUNC napi_delete_async_work
FUNC napi_queue_async_work
FUNC napi_cancel_async_work
FUNC napi_get_node_version
FUNC napi_get_version
FUNC napi_create_promise
FUNC napi_resolve_deferred
FUNC napi_reject_deferred
FUNC napi_is_promise
FUNC napi_run_script
FUNC napi_get_uv_event_loop

Native API接口说明

符号类型 符号名 备注
FUNC napi_run_script_path 运行JavaScript文件

  • Callback 异步模型

    • 用户在调用接口的时候,接口实现将异步执行任务,任务执行结果以参数的形式提供给用户注册的回调函数,这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。
  • Promise特点:

    • 对象的状态不受外界影响;
    • 一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。

附件链接:https://ost.51cto.com/resource/2372

本文作者:离北况归

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

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

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

#打卡不停更#三方库移植之napi开发[2]c/c++与js的数据类型转换(代码片段)

在《三方库移植之NAPI开发[1]—HelloOpenHarmonyNAPI》通过一个HelloOpenHarmonyNAPI样例讲述了NPAI接口开发基础知识。本文在其基础上修改hellonapi.cpp文件,介绍JS类型和C/C++数据类型之间的转换。开发基于最新的OpenHarmony3.2Beta3版本及其对应... 查看详情

基于stm32的串口收发讲解(hal库)#打卡不停更#(代码片段)

(基于STM32的串口收发程序(HAL库))介绍串口(UART通用异步收发器,TTL)通讯是一种设备间的串行全双工通讯方式。由于UART是异步传输,没有传输同步时钟,为了保证数据的正确性,UART采用16倍数据波特率的时钟进行采样。因为... 查看详情

#打卡不停更#openharmony-arkui(ts)声明式开发之列表拖动排列(代码片段)

作者:梁青松项目介绍本项目基于OpenHarmony的ArkUI框架:TS扩展的声明式开发范式,关于语法和概念直接看官网官方文档地址:基于TS扩展的声明式开发范式,因为OpenHarmony的API相对于HarmonyOS的API,功能上比较完善和成熟的,有些... 查看详情

#打卡不停更#-openharmony/docs开发入门(代码片段)

作者:朱子道杨成前言不管是作为软件开发的爱好者还是已经从事软件开发这个行业的从业者,对于接触一种全新的系统OpenHarmony。学习OpenHarmony,需要清楚OpenHarmony这个系统是什么,能干什么,今日分享从设备开发和应用开发两... 查看详情

4步成功将三方库——speexdsp移植到openharmony

4步成功将三方库——speexdsp移植到OpenHarmony​战码先锋,PR征集令(以下简称“战码先锋”)第二期正如火如荼地进行中,涉及OpenAtomOpenHarmony(以下简称“OpenHarmony”)主干仓、SIG仓、三方库,共计1000+代码仓任君挑战。在战码先... 查看详情

#打卡不停更#openharmony-应用开发入门指南(代码片段)

作者:余香鑫前言了解OpenHarmony能够开发一些简单的OpenHarmony应用一、了解OpenHarmonyOpenHarmony是由开放原子开源基金会(OpenAtomFoundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,搭建一个智能终端设备操作系统... 查看详情

#打卡不停更#简单的js鸿蒙小游戏——飞行棋之游戏逻辑(代码片段)

前言我们之前完成了游戏的基本布局,今天我们接着来讲下如何实现飞行棋的游戏逻辑。游戏逻辑掷骰子:随机地掷出点数1~6,根据骰子点数和当前阵营的棋子状态改变对应棋子的disabled属性,以控制该棋子是否可交互移动,若... 查看详情

#打卡不停更#简单的js鸿蒙小游戏——飞行棋之页面构建(代码片段)

前言飞行棋大家应该都玩过吧,红、绿、黄、蓝四名玩家轮流掷骰子移动棋子,争先到达终点,期间还要提防己方棋子不被击落。今天就先带大家学习下如何完成飞行棋游戏的简单布局。项目结构页面构建游戏的布局并不复杂,... 查看详情

#打卡不停更#openharmony数据转码应用开发实战(上)(代码片段)

背景OpenHarmony的应用开发支持C++、JS、eTS,从已有版本的演进路线来看,eTS是未来重点的技术路线。对于刚入门OpenHarmony应用开发的小伙伴来说,eTS可能比较陌生,如果有一个合适的实战项目来练手,那么对技术能力提升是非常有... 查看详情

中控考勤机的二次开发之灵活调配职工功能

...需要不定时的外派或支援2.客户员工发适时的到分公司去打卡3.客户员工的打卡数据需要及时的发放到目标分公司4.实现功能如下:1)各分公司需要及时从考勤机上取得员工的打卡验证数据2)各分公司需要及时上传员工考勤验证... 查看详情

#打卡不停更#harmonyos-基于arkui(js)实现虚拟摇杆组件(代码片段)

作者:杨尚晓前言虚拟摇杆在移动端游戏中是最常见看的,用来实现游戏中的精灵移动。本案例中使用jspai中的div和image组件来实现的虚拟摇杆组件,然后监听touch事件获取滑动的方向和位置x,y。开发环境说明工具版本:OpenHarmon... 查看详情

#打卡不停更#ffhopenharmony学生挑战赛分享-少儿语言教育app(代码片段)

Openharmony学生挑战赛经验分享前言本次参赛的项目是基于openHarmony开发的北向应用-少儿语言文化教育APP。从项目成立到初版成型再到参加比赛,这一路上遇到了不少困难,我也从团队协作、产品迭代、技术等方面学到了很多宝贵... 查看详情

#打卡不停更#智能喂食器(代码片段)

一、介绍​随着人们生活方式的不断改变,宠物猫在许多家庭中占有重要的地位,其凭借独立的个性和易于打理的饲养方式,成为当下上班族喜欢的宠物之一,人们更是把宠物猫和狗作为家庭的重要成员。有铲屎官表示,每月在... 查看详情

#打卡不停更#家庭健康管理平台(代码片段)

0.项目简介身体健康是一切生产生活的硬性基础。健康是福,一切安好,未来才可期。为什么经常跑步体重缺还在往上飘?突发紧急情况怎么处理?在数字时代,如何更好的为人们提供健康福祉、普及健康知识?如何进一步驱动... 查看详情

python之常用第三方库总结(代码片段)

...nbsp;在使用python进行开发的时候,经常我们需要借助一些第三方库,进行日常代码的开发工作.这里总结一些常用的类库 1.requests  Requests是用Python语言编写,基于urllib,采用Apache2Licensed开源协议的HTTP库。它比urllib更加方便... 查看详情

swift常用第三方库

网络Alamofire:http网络请求事件处理的框架。Moya:这是一个基于Alamofire的更高层网络请求封装抽象层。Reachability.swift:用来检查应用当前的网络连接状况。综合Perfect:swift的服务器端开发框架(针对于移动后端开发、网站和web应用程序... 查看详情

#打卡不停更#ffh浅析ability框架中stage模型与fa模型的差异(代码片段)

(#打卡不停更#【FFH】浅析Ability框架中Stage模型与FA模型的差异)Aility框架概述Ability是应用所具备能力的抽象,也是应用程序的基本组成单元。OpenHarmony与HarmonyOS的应用程序APP由一个或多个Hap包组成,每个Hap可以包含一个或多个Abilit... 查看详情

#打卡不停更#[图文并茂]packstack部署train版openstack(代码片段)

Packstack部署openstack-train介绍如何在centos7.9中使用packstack部署openstack-train。首先需要安装一个最小化安装的centos7.9,按照你的网络环境配置好网络,然后重启。这里我的环境是VMware虚拟机,ip是192.168.10.30.首先我们需要关闭防火墙... 查看详情