napi_value是NAPI中非常重要的数据结构,定义如下
typedef struct napi_value__* napi_value;
学过c语言的同学应该知道typedef是什么意思,他的作用就是定义类型别名。
typedef int intType;
intType a = 1;
但是我们发现搜遍Node.js的源码都找不到napi_value__定义,那这个定义是什么意思呢?c语言中,允许定义一个没有定义的结构体的指针。所以napi_value其实就是一个一级指针。他不需要类型信息,因为Node.js不会对他进行解引用。比如在c语言中有以下代码
int a = 1;int *p = &a;printf("%d", *p);
这是正确的用法,下面我们来改一下
int a = 1;void *p = &a;printf("%d", *p);
执行以上代码我们会得到以下错误信息
main.c: In function ‘main’:
main.c:7:14: warning: dereferencing ‘void *’ pointer
7 | printf("%d", *p);
| ^~
main.c:7:14: error: invalid use of void expression
因为在c语言中,对指针解引用的时候,需要有类型信息,比如char、int类型,这时候才能知道要读取多少字节的内存数据。所以改成以下代码就可以了。
int a = 1;void *p = &a;printf("%d", *(int *)p);
那么Node.js中的这个定义有什么用呢?我们看看他的用法。下面以NAPI中创建一个数组的API为例。
// 创建一个数组,对应js的数组
napi_status napi_create_array(napi_env env, napi_value* result) {
*result = v8impl::JsValueFromV8LocalValue(v8::Array::New(env->isolate)); return napi_clear_last_error(env);}
napi_create_array首先通过v8的接口v8::Array::New拿到一个数组对象。然后调用JsValueFromV8LocalValue,我们看看JsValueFromV8LocalValue的定义。
// 把v8类型转成napi类型inline napi_value JsValueFromV8LocalValue(v8::Local<v8::Value> local) { return reinterpret_cast<napi_value>(*local);}
我们看到JsValueFromV8LocalValue返回值是napi_value类型,即一个一级指针。他保存了v8创建的对象的地址信息。我们可以先不用深究*local是什么。接着执行了
*result = napi_value变量;
result类型是napi_value*,即二级指针,这样调用方就拿到了v8创建的对象。我们看一下具体的调用代码。
napi_value ret;napi_create_array(env, &ret);
执行以上代码后,ret就保存了v8对象的信息。那么这样做有什么好处呢?我们继续看一下对ret的使用。
napi_set_element(env, ret, index, vlaue;)
接着看一下napi_set_element。
// 设置key对应的值,key是数字
napi_status napi_set_element(napi_env env,
napi_value object,
uint32_t index,
napi_value value) {
v8::Local<v8::Context> context = env->context();
v8::Local<v8::Object> obj;
// 校验并转成对应的v8类型
CHECK_TO_OBJECT(env, context, obj, object);
v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value);
auto set_maybe = obj->Set(context, index, val); return GET_RETURN_STATUS(env);}
napi_set_element中最主要的是CHECK_TO_OBJECT。
#define CHECK_TO_OBJECT(env, context, result, src) \
CHECK_TO_TYPE((env), Object, (context), (result), (src), napi_object_expected)
是一个宏,继续展开
#define CHECK_TO_TYPE(env, type, context, result, src, status) \
do { \
CHECK_ARG((env), (src));
// 把napi类型转成v8类型,校验是否为空,非空则返回
\
auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \
CHECK_MAYBE_EMPTY((env), maybe, (status)); \
(result) = maybe.ToLocalChecked(); \
} while (0)
接着看V8LocalValueFromJsValue
// 把napi类型转成v8类型inline v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v) { v8::Local<v8::Value> local;
memcpy(static_cast<void*>(&local), &v, sizeof(v)); return local;}
V8LocalValueFromJsValue把napi_value v的值复制到local中,我们看看Local类的定义。
class Local { T* val_;}
即把v的值复制到了val_中,后续就可以按照v8的模式去使用了。
分析到这里,就结束了,那么napi_value到底有什么用呢?napi_value其实就是暂存v8对象信息的变量,他的用处就是可以保存任意类型的v8对象,因为不管什么类型的v8对象,他的地址大小是一样的,我们只需要面对napi_value就行,不需要关注v8的对象类型,当我们调用后续接口时只需要传入napi_value,Node.js就会帮我们处理好之后(转换成对应的v8类型)再调用v8的接口,否则用户就需要这样做。
v8::Local<v8::Object> v8value = v8::Object::New(...);
v8::Local<v8::Array> v8value = v8::Array::New(...);
总的来说,napi_value让我们可以不用关注v8的东西。最后看个小例子 例子1
#include <stdio.h>typedef struct napi_value__* napi_value;int main(){ int a = 2;
int *p = &a;
napi_value ptr = (napi_value)p;
printf("%d", *(int *)ptr); return 0;}
例子2
#include <stdio.h>typedef void* napi_value;int main(){ int a = 2;
int *p = &a;
napi_value ptr = (napi_value)p;
printf("%d", *(int *)ptr); return 0;}
我们看到其实使用void *类型也是可以的。
- 这是底线 -
敬请关注「Nodejs技术栈」微信公众号,获取优质文章,如需投稿可在后台留言与我取得联系。
▼
往期精彩推荐
▼