当前位置: 首页 > 工具软件 > napi > 使用案例 >

聊一下Node.js中NAPI的napi_value

易俊远
2023-12-01

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 *类型也是可以的。

 类似资料: