侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

NAPI 笔记 06:Addon Structure

barwe
2022-08-30 / 0 评论 / 1 点赞 / 1,593 阅读 / 4,897 字
温馨提示:
本文最后更新于 2023-06-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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_listvector 类似,表示由某个类型的元素组成的列表,只是其中的元素都是不可修改的常量

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>();}
1

评论区