NAPI笔记

NAPI简介

NAPI是什么

NAPI提供了一套C/C++接口,用于js和C/C++之间的转换调用,起到承上启下的作用。

NAPI官网链接

NAPI官网提供了详细的接口说明以及使用方法,链接如下,
http://nodejs.cn/api/n-api.html(中文)
https://nodejs.org/api/n-api.html (英文)

NAPI基本数据类型

基本数据类型简介

napi_status

NAPI中大多函数的返回值。枚举值,用于表示函数调用的结果。函数调用成功的时候返回napi_ok。

napi_env

表示上下文,一般使用的时候正常传递就行。

napi_value

对所有js的基本值的封装。

napi_async_work

用于管理异步工作线程。一般使用napi_create_async_work/napi_queue_async_work/napi_delete_async_work进行创建/排队/删除异步工作线程。

……

类型转换:C++ -> js

这里列举几个常见的数据类型,由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++

这里列举几个常见的数据类型,由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实现

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'); 

异步接口的使用之promise模式

HelloWorld('key2').then((value) => {
    console.log('value is ' + value);
}).catch((error) => {
    console.log('error: ' + error);
});

异步接口的使用之callback模式

HelloWorld('key2', (err, value) => {
    console.log('err.code: ' + err.code + " value: " + value);
});

参考文献

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

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值