NAPI提供了一套C/C++接口,用于js和C/C++之间的转换调用,起到承上启下的作用。
NAPI官网提供了详细的接口说明以及使用方法,链接如下,
http://nodejs.cn/api/n-api.html(中文)
https://nodejs.org/api/n-api.html (英文)
NAPI中大多函数的返回值。枚举值,用于表示函数调用的结果。函数调用成功的时候返回napi_ok。
表示上下文,一般使用的时候正常传递就行。
对所有js的基本值的封装。
用于管理异步工作线程。一般使用napi_create_async_work/napi_queue_async_work/napi_delete_async_work进行创建/排队/删除异步工作线程。
这里列举几个常见的数据类型,由C++转化成js数据的方法。
napi_env env;
napi_value value;
// int32_t -> number
int32_t int32Data = 1;
napi_create_int32(env, int32Data, &value)
// string
std::string stringData;
napi_create_string_utf8(env, stringData.c_str(), NAPI_AUTO_LENGTH, &value);
// bool -> boolean
bool boolData = false;
napi_get_boolean(env, boolData, &value);
// object: 这里举例的是包含point属性的一个object
napi_create_object(env, &value);
int32_t point = 0;
napi_value npoint;
napi_create_int32(env, point, &npoint);
napi_set_named_property(env, value, "point", npoint);
// vector<int32_t> -> array<number>
std::vector<int32_t> vecDatas;
vecDatas.push_back(1);
napi_create_array(env, &value);
size_t index = 0;
for (auto& vecData : vecDatas) {
napi_value id;
napi_create_int32(env, vecData, &id);
napi_set_element(env, value, index, id);
index++;
}
这里列举几个常见的数据类型,由js转化成C++数据的方法。
napi_env env;
napi_value value;
// string
char outBuffer[1024 + 1] = {0};
size_t outSize = 0;
napi_get_value_string_utf8(env, value, outBuffer, 1024, &outSize);
std::string result = std::string(outBuffer);
// bool
bool result = false;
napi_get_value_bool(env, value, &result);
// number
int32_t result = 0;
napi_get_value_int32(env, args, &result);
NAPI的实现应先进行模块注册。一般来说,每个js接口都应有一个绑定的C++接口进行底层逻辑的处理,所以模块注册的时候,也需要将对应的js接口和C++接口进行绑定。js接口有同步和异步两种模式,并且,异步模式中也有promise模式和callback模式。下文将对这三种类型的接口进行举例说明。
这里列举几个接口示例,下文的NAPI接口实现也是在以下几个接口的基础上实现的。
// js接口
function helloWorld(key: string): string; // 同步调用接口
function helloWorld(key: string, callback: AsyncCallback<string>): void; // 异步调用接口,callback模式
function helloWorld(key: string): Promise<string>; // 异步调用接口,promise模式
// C++接口
static napi_value HelloWorld(napi_env env, napi_callback_info info);
模块注册的时候应该将js接口和C++接口进行绑定并暴露出去。
#define DECLARE_NAPI_FUNCTION(name, func) \
{ \
name, 0, func, 0, 0, 0, napi_default, 0 \
}
// 模块初始化函数,用于绑定js和C++对应的接口
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor descriptor[] = {
DECLARE_NAPI_FUNCTION("helloWorld", HelloWorld),
};
napi_define_properties(env, exports, sizeof(descriptor) / sizeof(descriptor[0]), descriptor);
return exports;
}
NAPI_MODULE(hello, Init) // 注册hello模块
当napi接口被调用时,最好对入参进行类型检查,以保障后续的逻辑处理。以下使用napi_typeof接口进行参数类型的判断,详细的接口参数说明可见NAPI官网。
bool CheckNumber(napi_env env, napi_value value)
napi_valuetype type;
napi_status status = napi_typeof(env, value, &type);
if (status != napi_ok) {
return false;
}
if (type != napi_number) {
return false;
}
return true;
}
// napi_valuetype的枚举值如下
typedef enum {
napi_undefined,
napi_null,
napi_boolean,
napi_number,
napi_string,
napi_symbol,
napi_object,
napi_function,
napi_external,
napi_bigint,
} napi_valuetype;
同步调用的接口,调用后会阻塞住js线程,直至接口的结果返回,一般逻辑处理简单且不耗时的接口会选择同步处理。
function helloWorld(key: string): string; // js接口
static napi_value HelloWorld(napi_env env, napi_callback_info info); // C++接口
NAPI的函数实现如下
static napi_value HelloWorld(napi_env env, napi_callback_info info)
{
// 获取js的入参数据
size_t argc = 1; // 参数个数
napi_value argv = nullptr; // 参数的值
napi_value thisVar = nullptr; // js对象的this指针
void* data = nullptr; // 回调数据指针
napi_get_cb_info(env, info, &argc, &argv, &thisVar, &data)
NAPI_ASSERT(env, argc == 1, "requires 1 parameter");
// 判断入参的类型是否正确
napi_valuetype type = napi_undefined;
napi_typeof(env, argv, &type);
NAPI_ASSERT(env, type == napi_string, "the type of arg is not string");
// 解析入参
char outBuffer[1024 + 1] = {0};
size_t outSize = 0;
napi_get_value_string_utf8(env, argv, outBuffer, 1024, &outSize);
std::string key = std::string(outBuffer);
// 处理入参并向js返回结果
napi_value result = nullptr;
std::string value = key + "HelloWorld";
napi_create_string_utf8(env, value.c_str(), NAPI_AUTO_LENGTH, &result);
return result;
}
由于js的单线程处理方式,为提高效率,使用异步回调的接口较多。为方便,以下NAPI的实现将promise模式和callback模式放到一起处理,也可分开实现。
// js接口
function helloWorld(key: string, callback: AsyncCallback<string>): void; // 异步调用接口,callback模式
function helloWorld(key: string): Promise<string>; // 异步调用接口,promise模式
// C++接口
static napi_value HelloWorld(napi_env env, napi_callback_info info);
NAPI的函数实现如下
struct HelloWorldCallBack {
napi_async_work work;
napi_ref callbackRef = nullptr; // 用于callback模式
napi_deferred deferred; // 用于promise模式
std::string key = "";
std::string value = "";
bool ret = false;
}
static napi_value HelloWorld(napi_env env, napi_callback_info info)
{
// 获取js的入参数据
size_t argc = 2; // 参数个数
napi_value argv[2] = {0}; // 参数的值
napi_value thisVar = nullptr; // js对象的this指针
void* data = nullptr; // 回调数据指针
napi_get_cb_info(env, info, &argc, argv, &thisVar, &data)
NAPI_ASSERT(env, ((argc >= 1) && (argc <= 2)), "requires 1 parameter");
// 判断入参的类型是否正确
napi_valuetype type = napi_undefined;
napi_typeof(env, argv[0], &type);
NAPI_ASSERT(env, type == napi_string, "the type of argv[0] is not string");
if (argc == 2) {
napi_typeof(env, argv[1], &type);
NAPI_ASSERT(env, type == napi_function, "the type of argv[1] is not function");
}
HelloWorldCallBack* callback = new HelloWorldCallBack();
// 解析入参key
char outBuffer[1024 + 1] = {0};
size_t outSize = 0;
napi_get_value_string_utf8(env, argv[0], outBuffer, 1024, &outSize);
callback->key = std::string(outBuffer);
// 解析入参callback
if (argc == 2) {
napi_create_reference(env, argv[1], 1, &callback->callbackRef);
}
napi_value promise = nullptr;
// callback ref为空,说明是promise模式,反之是callback模式
if (!callback->callbackRef) {
napi_create_promise(env, &callback->deferred, &promise);
} else {
napi_get_undefined(env, &promise);
}
napi_value resource = nullptr;
napi_create_string_utf8(env, "HelloWorld", NAPI_AUTO_LENGTH, &resource);
// 创建异步工作
napi_create_async_work(env, nullptr, resource,
[](napi_env env, void* data) {
// napi_async_execute_callback方法,该方法会在新起的线程中运行,一般在此函数中调用C++接口进行异步处理操作。
HelloWorldCallBack* callback = static_cast<HelloWorldCallBack*>(data);
if (callback->key == "") {
callback->ret = false;
} else {
callback->ret = true;
callback->value = callback->key + "HelloWorld";
}
},
[](napi_env env, napi_status status, void* data) {
// napi_async_complete_callback方法,该方法在js的主线程中运行,一般用于给js侧返回结果,此处最好不要进行耗时操作。
HelloWorldCallBack* callback = static_cast<HelloWorldCallBack*>(data);
napi_value result[2] = {0};
if (callback->ret) {
napi_create_string_utf8(env, callback->value.c_str(), NAPI_AUTO_LENGTH, &result[1]);
} else {
napi_get_undefined(env, &result[1]);
}
if (callback->callbackRef) {
// callback模式
// 根据execute函数的结果进行err code的赋值
napi_value errCode = nullptr;
napi_create_int32(env, callback->ret ? 0 : 1, &errCode);
napi_create_object(env, &result[0]);
napi_set_named_property(env, result[0], "code", errCode);
// 调用对应的js的callback函数
napi_value callback = nullptr;
napi_get_reference_value(env, callback->callbackRef, &callback);
napi_value returnValue;
// 此函数的最后一个参数不可传nullptr,否则程序会崩溃
napi_call_function(env, nullptr, callback, sizeof(result) / sizeof(result[0]), result, returnValue);
napi_delete_reference(env, callback->callbackRef);
} else {
// promise模式
if (callback->ret) {
napi_resolve_deferred(env, callback->deferred, result[1]);
} else {
napi_reject_deferred(env, callback->deferred, result[1]);
}
}
napi_delete_async_work(env, callback->work); // 异步任务完成之后删除任务
delete callback;
}, (void*)callback, &callback->work);
napi_queue_async_work(env, callback->work); //异步任务入队列,排队执行
return promise;
}
以下对上文中提出的js接口进行应用举例。
var value = HelloWorld('key1');
HelloWorld('key2').then((value) => {
console.log('value is ' + value);
}).catch((error) => {
console.log('error: ' + error);
});
HelloWorld('key2', (err, value) => {
console.log('err.code: ' + err.code + " value: " + value);
});
https://ost.51cto.com/posts/8390