summary
- InstanceWrap 实现了 C++ 对象和 JavaScript 对象的转换,提供了三个方法用于暴露方法和属性给 JavaScript
- 扩展 Addon 类实现自定义 addon,其构造函数的参数为 env 和 exports,内部使用 DefineAddon 导出对象
class InstanceWrap
https://github.com/nodejs/node-addon-api/blob/main/doc/instance_wrap.md
InstanceWrap<T>
作为ObjectWrap<T>
和Addon<T>
的基类,连接起了 JavaScript 对象和 C++ 对象:
- 作为
Addon<T>
的父类,它向 JavaScript 暴露了 C++ 函数,这样可以在 JavaScript 中调用 C++ 方法 - 作为
ObjectWrap<T>
的父类,它将 JavaScript 对象转化为 C++ 对象,这样可以在 C++ 中调用 JavaScript 对象的实例方法
static method InstanceMethod
用于描述一个需要在 JavaScript 对象上暴露的方法。
template <typename T> // A
static Napi::ClassPropertyDescriptor<T> // B
Napi::InstanceWtap<T>::InstanceMethod( // C
const char* utf8name, // D
InstanceVoidMethodCallback method, // E
napi_property_attributes attributes = napi_default, // F
void* data = nullptr // G
);
- A: 实例化
InstanceWrap<T>
需要一个泛型 - B: 该方法是
InstanceWrap<T>
的静态方法,且其返回值类型为ClassPropertyDescriptor<T>
,表明其为一个属性描述对象 - C: 方法的完整索引是
Napi::InstanceWtap<T>::InstanceMethod()
- D: 要暴露出去的方法名称,该参数可以重载:
const char*
Napi::Symbol
- E: 被暴露的方法对应的 C++ 函数,该参数可以重载:
InstanceVoidMethodCallback
: 绑定的函数没有返回值,其形式必须为void myfunc(const Napi::CallbackInfo& info);
InstanceMethodCallback
: 绑定的函数有返回值,其形式必须为Mapi::Value myfunc(const Mapi::CallbackInfo& info);
- F
- G: 当在 JavaScript 中调用此方法时,用户传递的自定义数据
D 和 E 直接决定了要暴露哪个函数出去,暴露的函数名称是什么(这个名称可以直接在 JavaScript 中被调用)。
static method InstanceValue
用于描述一个需要在 JavaScript 对象上暴露的值(对象)。
template <typename T>
static Napi::ClassPropertyDescriptor<T>
Napi::InstanceWrap<T>::InstanceValue(
const char* name,
Napi::Value value,
napi_property_attributes attributes = napi_default
);
第一个参数是需要暴露出去的变量名称,可以重载为Napi::Symbool
类型。
第二个参数是需要暴露出去的值(对象),类型为Napi::Value
。
该方法只能用于在 JavaScript 中访问变量的值,而不能设置变量的值。
static method InstanceAccessor
顾名思义,为指定的变量暴露一对 getter & setter,使得在 JavaScript 中 既可以访问、又可以设置 变量的值。
template <typename T>
static Napi::ClassPropertyDescriptor<T>
Napi::InstanceWrap<T>::InstanceAccessor(
const char* utf8name,
InstanceGetterCallback getter,
InstanceSetterCallback setter,
napi_property_attributes attributes = napi_default,
void* data = nullptr
);
class Addon
https://github.com/nodejs/node-addon-api/blob/main/doc/addon.md
Napi::Addon<T>
继承自Napi::InstanceWrap<T>
,用于将 C++ 对象暴露给 JavaScript。
在 Node.js 多个线程中分别引入相同的 addon 模块,或者在一个线程内多次引入相同的 addon 模块,都可能会导致不安全的操纵模块内的全局数据。
NAPI 通过Addon
类来定义一个整体 addon 实例。开发者可以通过继承Addon
开发自己的 Addon,每次引入时新建的子类实例都会自动成为 addon 实例。
这个整体意义上的 addon 实例由 Node.js 维护,这样就解决了多次引入时创建多个实例的问题。
constructor
Napi::Addon(Napi::Env env, Napi::Object exports);
env
: 引入该 addon 的 JavaScript 环境exports
: 该 addon 暴露给 JavaScript 的对象
通常情况下,构造函数通过调用DefineAddon()
方法将需要暴露出去的方法、属性和值添加到exports
对象上。
method DefineAddon
InstanceWrap<T>
提供了三个静态方法(InstanceMethod
, InstanceAccessor
& InstanceValue
)用于声明需要暴露出去的方法、可读可写属性和可读属性。
将这些需要暴露的方法和属性组合起来,传递给Addon<T>
实例的DefineAddon()
方法构建一个 addon 实例,该方法通常在实例化Addon
时的构造函数中被调用。
template <typename T>
void Napi::Addon<T>::DefineAddon(
Napi::Object exports,
const std::initializer_list<PropertyDescriptor>& properties
);
exports
: 将 addon 实例作为一个对象返回给 Node.js,该对象可转化为 JavaScript 中的对象properties
: 需要添加到返回对象上的各种被暴露的方法和属性
initializer_list 与 vector 类似,表示由某个类型的元素组成的列表,只是其中的元素都是不可修改的常量
method DefineProperties
为一个对象添加需要暴露的方法和属性。
该方法与DefineAddon()
方法功能上有重合的部分,但是DefineAddon()
方法是用于将对象返回给 Node.js。
template <typename T>
Napi::Object Napi::Addon<T>::DefineProperties(
Napi::Object object,
const std::initializer_list<PropertyDescriptor>& properties
);
该方法会返回被添加了新的方法和属性的新对象。
example addon
下面分析一下官方提供的例子: 用 C++ 实现一个计数器。
首先我们需要一个成员属性来存储计数器的值:
#include <napi.h>
class ExampleAddon : public Napi::Addon<ExampleAddon> {
private:
uint32_t value = 0;
}
按照约定,每个 addon 实例的 实例属性 是格子独立且唯一的。
然后我们需要定义一些 待暴露的 方法用于操纵数据,在计数器例子中我们需要一个增加计数器和减少计数器的方法。
每个待暴露的方法都需要通过InstanceWrap::InstanceMethod()
注册给 addon 实例,按照有无返回值,可以分为下面两种:
// 无返回值的方法,例如情况计数器
void Reset(const Napi::CallbackInfo& info);
// 有返回值的方法,例如增加和减少计数器
Napi::Value Increment(const Napi::Callback& info);
Napi::Value Decrement(const Napi::Callback& info);
下面实现这几个方法:
class ExampleAddon : public Napi::Addon<ExampleAddon> { private: uint32_t value = 0; void Reset(const Napi::CallbackInfo& info) { value = 0; } Napi::Value Increment(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), ++value); } Napi::Value Decrement(const Napi::CallbackInfo& info) { return Napi::Number::New(info.Env(), --value); }}
在构造函数中通过DefineAddon()
方法将我们实现的 待暴露方法 绑定到导出对象上返回给 Node.js
class ExampleAddon : public Napi::Addon<ExampleAddon> { public: ExampleAddon(Napi::Env env, Napi::Object exports) { DefineAddon(exports, { // 将函数挂载到导出对象的 increment 属性上 InstanceMethod("increment", &ExampleAddon::Increment), // 创建一个子对象{},将函数挂载到子对象的 decrement 属性上,然后将子对象挂载到导出对象上 InstanceValue("subObject", DefineProperties(Napi::Object::New(env), { InstanceMethod("decrement", &ExampleAddon::Decrement) })) }) } private: /** ... */}
这样我们就能在 JavaScript 中使用这两个方法:
const exampleAddon = require('example_addon')exampleAddon.increment()exampleAddon.subObject.decrement()
每个 addon 实例都是和 env 实例绑定的,在待暴露方法中可以通过 env 实例获取 addon 实例:
void ExampleBinding(const CallbackInfo& info) { ExampleAddon* addon = info.Env().getInstanceData<ExampleAddon>();}
评论区