笔者在前 5 小节里讲述了在 OpenHarmony 上通过 NAPI 的方式实现了 JS 调用 C++的能力,但是这些实现都是同步的,本节笔者简单介绍一下 NAPI 的异步实现。
约定编程规范
ArkUI 开发框架对外提供的 API 命名是需遵守一定规范的,以 @ohos.display
模块提供的 API 为例,源码如下所示:
declare namespace display {
function getDefaultDisplay(callback: AsyncCallback<Display>): void;
function getDefaultDisplay(): Promise<Display>;
function getDefaultDisplaySync(): Display;
}
根据该模块提供的方法,根据方法的命名规则可以得出 2
条规范:
-
同步调用:
- 方法名+ Sync 关键字,如:
getMd5Sync():string
。
- 方法名+ Sync 关键字,如:
-
异步调用:
- 需要提供 AsyncCallback 和 Promise 的实现,如:
getMd5(): Promise<string>
、getMd5(callback: AsyncCallback<Display>)
。
- 需要提供 AsyncCallback 和 Promise 的实现,如:
因此,我们在 index.d.ts
中声明 NAPI 方法时也按照系统约定的规范来。
定义异步方法
笔者在第 5 小结实现了 MD5 的计算,本节笔者把 MD5 的实现放在异步线程中,先在 index.d.ts
声明 JS 侧的方法,如下所示:
export const add: (a: number, b: number) => number;
// 声明异步方法
export function getMd5(value: string, callback: (md5: string) => void): void;
export function getMd5(value: string): Promise<string>;
// 声明同步方法
export function getMd5Sync(value: string): string;
getMd5Sync()
表示同步实现 MD5 的计算,getMd5()
表示异步实现 MD5 的调用。
实现异步方法
声明完 JS 端的方法后,接着在 hello.cpp
中实现对应的方法,步骤如下:
-
添加映射
在
hello.cpp
的 Init() 方法里添加 JS 端的方法映射,代码如下所示:
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getMd5", nullptr, GetMd5, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
"getMd5Sync"
和 GetMd5Sync
分别表示 JS 端和 C++ 端的方法,通过 napi_define_properties() 把他们映射在一起。
- 方法实现
getMd5() 的 C++ 端代码如下所示:
// 定义异步线程执行中需要的上下文环境
struct Md5Context {
// 异步 worker
napi_async_work work;
// 对应 JS 端的 callback 函数
napi_ref callback;
// 对应 JS 端的 promise 对象
napi_deferred promise;
// 传递进来的参数
string params;
// 计算后的结果
string result;
};
// 在子线程中执行
static void doInBackground(napi_env env, void *data) {
Md5Context *md5Context = (Md5Context *)data;
// 模拟耗时操作,进行 MD5 计算
string md5 = MD5(md5Context->params).toStr();
// 计算后的 MD5 字存储到 result 中
md5Context->result = md5;
// 模拟耗时操作,让当前线程休眠 3 秒钟
std::this_thread::sleep_for(std::chrono::seconds(3));
}
// 切换到主线程
static void onPostExecutor(napi_env env, napi_status status, void *data) {
Md5Context *md5Context = (Md5Context *)data;
napi_value returnValue;
if (napi_ok !=
napi_create_string_utf8(env, md5Context->result.c_str(), md5Context->result.length(), &returnValue)) {
delete md5Context;
md5Context = nullptr;
napi_throw_error(env, "-111", "napi_create_string_utf8: error");
return;
}
if (md5Context->callback) {
// 取出缓存的 js 端的 callback
napi_value callback;
if (napi_ok != napi_get_reference_value(env, md5Context->callback, &callback)) {
delete md5Context;
md5Context = nullptr;
napi_throw_error(env, "-111", "napi_get_reference_value error");
return;
}
napi_value tempValue;
// 调用 callback,把值回调给 JS 端
napi_call_function(env, nullptr, callback, 1, &returnValue, &tempValue);
// 删除 callback
napi_delete_reference(env, md5Context->callback);
} else {
// 以 promise 的形式回调数据
if (napi_ok != napi_resolve_deferred(env, md5Context->promise, returnValue)) {
delete md5Context;
md5Context = nullptr;
napi_throw_error(env, "-111", "napi_resolve_deferred error");
}
}
// 删除异步任务并释放资源
napi_delete_async_work(env, md5Context->work);
delete md5Context;
md5Context = nullptr;
}
static napi_value GetMd5(napi_env env, napi_callback_info info) {
// 1、从 info 中读取 JS 传递过来的参数放入 args 里
size_t argc = 2;
napi_value args[2] = {nullptr};
if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
napi_throw_error(env, "-1001", "napi_get_cb_info error");
return nullptr;
}
// 2、读取传入的参数类型
napi_valuetype stringType = napi_undefined;
if (napi_ok != napi_typeof(env, args[0], &stringType)) {
napi_throw_error(env, "-1002", "napi_typeof string error");
return nullptr;
}
// 3、传入的 string 如果为 null 或者 undefined 则抛异常
if (napi_null == stringType || napi_undefined == stringType) {
napi_throw_error(env, "-1003", "input params null or undefined");
return nullptr;
}
// 4、读取传入的 string 内容长度
size_t length = 0;
if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
napi_throw_error(env, "-1004", "get string length error");
return nullptr;
}
// 5、判断传入的 string 长度是否符合
if (0 == length) {
napi_throw_error(env, "-1005", "string length can't be zero");
return nullptr;
}
// 6、读取传入的 string 长度读取内容
char *buffer = new char[length + 1];
if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
delete[] buffer;
buffer = nullptr;
napi_throw_error(env, "-1006", "napi_get_value_string_utf8 string error");
return nullptr;
}
// 7、读取 JS 有没有传递 callback,如果 callback 为 null 就表示是 promise 的回调方式
napi_valuetype callbackType = napi_undefined;
napi_status callbackStatus = napi_typeof(env, args[1], &callbackType);
if (napi_ok != callbackStatus && napi_invalid_arg != callbackStatus) {
delete[] buffer;
buffer = nullptr;
napi_throw_error(env, "-1004", "napi_typeof function error");
return nullptr;
}
// 8、创建一个异步线程需要的数据 model,把传递过来的参数加入进去做下缓存
auto context = new Md5Context();
context->params = buffer;
napi_value returnValue = nullptr;
// 9、判断是 callback 的回调方式还是 promise 的回调方式
if (napi_function == callbackType) {
// 如果是 callback 的回调方式,需要创建 callback 的引用
napi_ref callback;
if (napi_ok != napi_create_reference(env, args[1], 1, &callback)) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_reference error");
return nullptr;
}
// 缓存 callback
context->callback = callback;
// 临时返回一个 undefined 值给 JS 端
napi_get_undefined(env, &returnValue);
} else {
// promise 的回调方式,创建一个 Promise 的引用
napi_deferred promise;
if (napi_ok != napi_create_promise(env, &promise, &returnValue)) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_promise error");
return nullptr;
}
// 缓存 promise
context->promise = promise;
}
napi_value resourceName;
if (napi_ok != napi_create_string_utf8(env, "GetMd5", NAPI_AUTO_LENGTH, &resourceName)) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_string_utf8 resourceName error");
return nullptr;
}
// 10、创建一个异步任务
napi_async_work asyWork;
napi_status status =
napi_create_async_work(env, nullptr, resourceName, doInBackground, onPostExecutor, (void *)context, &asyWork);
if (napi_ok != status) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_async_work error");
return nullptr;
}
// 11、保存异步任务
context->work = asyWork;
// 12、添加进异步队列
napi_queue_async_work(env, asyWork);
return returnValue;
}
getMd5() 的代码比较多,笔者添加的注释比较清楚,前 6 个小步骤是对传递进来的参数做基础校验,第 7 步是根据参数判断当前异步执行的回调方式是 Promise 还是 Callback。第 8 步创建了一个 Md5Context
对象,它的作用是把当前相关参数缓存下来目的是接下来在异步线程里使用这些参数,第 9 步根据异步回调的方法创建 Promise 或者 Callback 然后把他们保存在 Md5Context
对象里。第 10 步创建一个异步任务,然后把异步任务添加进异步队列中。
napi_create_async_work() 方法的第 3 、 4 个参数需要注意,doInBackground() 方法是在异步线程中执行的,onPostExecutor() 方法在异步线程结束后切换到主线程中执行。
- 完整代码
hello.cpp
全部代码如下所示:
#include <cstddef>
#include <cstring>
#include "napi/native_api.h"
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <node_api.h>
#include <node_api_types.h>
#include <string>
#include <thread>
#include "./md5/md5.h"
// 定义异步线程执行中需要的上下文环境
struct Md5Context {
// 异步 worker
napi_async_work work;
// 对应 JS 端的 callback 函数
napi_ref callback;
// 对应 JS 端的 promise 对象
napi_deferred promise;
// 传递进来的参数
string params;
// 计算后的结果
string result;
};
static void doInBackground(napi_env env, void *data) {
Md5Context *md5Context = (Md5Context *)data;
// 模拟耗时操作,进行 MD5 计算
string md5 = MD5(md5Context->params).toStr();
// 计算后的 MD5 字存储到 result 中
md5Context->result = md5;
// 模拟耗时操作,让当前线程休眠 3 秒钟
std::this_thread::sleep_for(std::chrono::seconds(3));
}
static void onPostExecutor(napi_env env, napi_status status, void *data) {
Md5Context *md5Context = (Md5Context *)data;
napi_value returnValue;
if (napi_ok !=
napi_create_string_utf8(env, md5Context->result.c_str(), md5Context->result.length(), &returnValue)) {
delete md5Context;
md5Context = nullptr;
napi_throw_error(env, "-111", "napi_create_string_utf8: error");
return;
}
if (md5Context->callback) {
// 取出缓存的 js 端的 callback
napi_value callback;
if (napi_ok != napi_get_reference_value(env, md5Context->callback, &callback)) {
delete md5Context;
md5Context = nullptr;
napi_throw_error(env, "-111", "napi_get_reference_value error");
return;
}
napi_value tempValue;
// 调用 callback,把值回调给 JS 端
napi_call_function(env, nullptr, callback, 1, &returnValue, &tempValue);
// 删除 callback
napi_delete_reference(env, md5Context->callback);
} else {
// 以 promise 的形式回调数据
if (napi_ok != napi_resolve_deferred(env, md5Context->promise, returnValue)) {
delete md5Context;
md5Context = nullptr;
napi_throw_error(env, "-111", "napi_resolve_deferred error");
}
}
// 删除异步任务并释放资源
napi_delete_async_work(env, md5Context->work);
delete md5Context;
md5Context = nullptr;
}
static napi_value GetMd5(napi_env env, napi_callback_info info) {
// 1、从 info 中读取 JS 传递过来的参数放入 args 里
size_t argc = 2;
napi_value args[2] = {nullptr};
if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
napi_throw_error(env, "-1001", "napi_get_cb_info error");
return nullptr;
}
// 2、读取传入的参数类型
napi_valuetype stringType = napi_undefined;
if (napi_ok != napi_typeof(env, args[0], &stringType)) {
napi_throw_error(env, "-1002", "napi_typeof string error");
return nullptr;
}
// 3、传入的 string 如果为 null 或者 undefined 则抛异常
if (napi_null == stringType || napi_undefined == stringType) {
napi_throw_error(env, "-1003", "input params null or undefined");
return nullptr;
}
// 4、读取传入的 string 内容长度
size_t length = 0;
if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
napi_throw_error(env, "-1004", "get string length error");
return nullptr;
}
// 5、判断传入的 string 长度是否符合
if (0 == length) {
napi_throw_error(env, "-1005", "string length can't be zero");
return nullptr;
}
// 6、读取传入的 string 长度读取内容
char *buffer = new char[length + 1];
if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
delete[] buffer;
buffer = nullptr;
napi_throw_error(env, "-1006", "napi_get_value_string_utf8 string error");
return nullptr;
}
// 7、读取 JS 有没有传递 callback,如果 callback 为 null 就表示是 promise 的回调方式
napi_valuetype callbackType = napi_undefined;
napi_status callbackStatus = napi_typeof(env, args[1], &callbackType);
if (napi_ok != callbackStatus && napi_invalid_arg != callbackStatus) {
delete[] buffer;
buffer = nullptr;
napi_throw_error(env, "-1004", "napi_typeof function error");
return nullptr;
}
// 8、创建一个异步线程需要的数据 model,把传递过来的参数加入进去做下缓存
auto context = new Md5Context();
context->params = buffer;
napi_value returnValue = nullptr;
// 9、判断是 callback 的回调方式还是 promise 的回调方式
if (napi_function == callbackType) {
// 如果是 callback 的回调方式,需要创建 callback 的引用
napi_ref callback;
if (napi_ok != napi_create_reference(env, args[1], 1, &callback)) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_reference error");
return nullptr;
}
// 缓存 callback
context->callback = callback;
// 临时返回一个 undefined 值给 JS 端
napi_get_undefined(env, &returnValue);
} else {
// promise 的回调方式,创建一个 Promise 的引用
napi_deferred promise;
if (napi_ok != napi_create_promise(env, &promise, &returnValue)) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_promise error");
return nullptr;
}
// 缓存 promise
context->promise = promise;
}
napi_value resourceName;
if (napi_ok != napi_create_string_utf8(env, "GetMd5", NAPI_AUTO_LENGTH, &resourceName)) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_string_utf8 resourceName error");
return nullptr;
}
// 10、创建一个异步任务
napi_async_work asyWork;
napi_status status =
napi_create_async_work(env, nullptr, resourceName, doInBackground, onPostExecutor, (void *)context, &asyWork);
if (napi_ok != status) {
delete[] buffer;
delete context;
buffer = nullptr;
context = nullptr;
napi_throw_error(env, "-11", "napi_create_async_work error");
return nullptr;
}
// 11、保存异步任务
context->work = asyWork;
// 12、添加进异步队列
napi_queue_async_work(env, asyWork);
return returnValue;
}
static napi_value GetMd5Sync(napi_env env, napi_callback_info info) {
// 1、从info中取出JS传递过来的参数放入args
size_t argc = 1;
napi_value args[1] = {nullptr};
if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) {
napi_throw_error(env, "-1000", "napi_get_cb_info error");
return nullptr;
}
// 2、获取参数的类型
napi_valuetype stringType;
if (napi_ok != napi_typeof(env, args[0], &stringType)) {
napi_throw_error(env, "-1001", "napi_typeof error");
return nullptr;
}
// 3、如果参数为null或者undefined,则抛异常
if (napi_null == stringType || napi_undefined == stringType) {
napi_throw_error(env, "-1002", "the param can't be null");
return nullptr;
}
// 4、获取传递的string长度
size_t length = 0;
if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
napi_throw_error(env, "-1003", "napi_get_value_string_utf8 error");
return nullptr;
}
// 5、如果传递的是"",则抛异常
if (length == 0) {
napi_throw_error(env, "-1004", "the param length invalid");
return nullptr;
}
// 6、读取传递的string参数放入buffer中
char *buffer = new char[length + 1];
if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
delete[] buffer;
buffer = nullptr;
napi_throw_error(env, "-1005", "napi_get_value_string_utf8 error");
return nullptr;
}
// 7、计算MD5加密操作
std::string str = buffer;
str = MD5(str).toStr();
// 8、把C++数据转成napi_value并返回
napi_value value = nullptr;
const char *md5 = str.c_str();
if (napi_ok != napi_create_string_utf8(env, md5, strlen(md5), &value)) {
delete[] buffer;
buffer = nullptr;
napi_throw_error(env, "-1006", "napi_create_string_utf8 error");
return nullptr;
}
// 9、资源清理
delete[] buffer;
buffer = nullptr;
return value;
}
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},
{"getMd5Sync", nullptr, GetMd5Sync, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getMd5", nullptr, GetMd5, 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);
}
Index.ets
的测试代码如下:
import testNapi from 'libentry.so';
@Entry @Component struct Index {
@State message: string = 'Hello,OpenHarmony'
build() {
Column({ space: 10 }) {
Text(this.message)
.fontSize(20)
Button("同步回调")
.onClick(() => {
this.message = testNapi.getMd5Sync("Hello, OpenHarmony")
})
Button("异步 Callback 回调")
.onClick(() => {
this.message = "计算中...";
testNapi.getMd5("Hello, OpenHarmony", (md5: string) => {
this.message = md5;
});
})
Button("异步 Promise 回调")
.onClick(() => {
this.message = "计算中...";
testNapi.getMd5("Hello, OpenHarmony").then((md5: string) => {
this.message = md5;
}).catch((error: Error) => {
this.message = "error: " + error;
})
})
}
.padding(10)
.width('100%')
.height("100%")
}
}
样例运行结果如下图所示:
小结
本节笔者简单讲述了 NAPI 的异步实现方式,下一小节笔者从源码的角度给大家讲解一下 NAPI 的实现原理,敬请期待……
如果想更深入的学习 OpenHarmony 开发的内容,可以参考以下学习文档:
OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy
《OpenHarmony源码解析》:https://qr18.cn/CgxrRy
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
系统架构分析:https://qr18.cn/CgxrRy
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……