N-api
仅记录对N-api
的使用方法以及函数签名。在Windows
平台下,如果我们安装的是32 位的Nodejs,N-api
应该是生成32
位的.node
文件,而且并没有启用UNICODE
的支持,即所有_WIN64, UNICODE
下的宏定义都是无效的。所以在Winodws 版本下的API 函数最好都调用A
结尾的。
// 在Windows 系统下编译程序时,会针对目标平台改变某些宏定义。
// 据此,我们可以输出这些结果,来判断编译结果的目标位宽
#ifndef SIZE_MAX
#ifdef _WIN64
#define SIZE_MAX 0xffffffffffffffffui64
#else
#define SIZE_MAX 0xffffffffui32
#endif
#endif
// 而napi 自身的宏定义中,也只有关于_WIN32 的声明
#ifndef NAPI_EXTERN
#ifdef _WIN32
#define NAPI_EXTERN __declspec(dllexport)
#elif defined(__wasm32__)
#define NAPI_EXTERN __attribute__((visibility("default"))) \ __attribute__((__import_module__("napi")))
#else
#define NAPI_EXTERN __attribute__((visibility("default")))
#endif
#endif
一般函数
最原始的N-api
的代码是C
风格的。所以函数的调用也都是面向过程的风格,写起来会比较啰嗦。
#include <assert.h>
#include <node_api.h>
/**
* 函数:n-api 的函数一般由五部分组成
* - 函数名。一般用static 修饰符
* - 形参1:napi_env。用于获取函数调用时的环境
* - 形参2:napi_callback_info。用于传递函数调用时的实参
* - 返回值:napi_value。napi 中所有的数据都使用napi_value 封装
* - 运行状态:napi_status。通过assert 断言函数运行状态
*/
static napi_value Method(napi_env env, napi_callback_info info) {
napi_status status;
napi_value world;
// 这里我修改了示例代码,用来验证编译的目标平台是32 位的
// status = napi_create_string_utf8(env, "world", 5, &world);
status = napi_create_bigint_int64(env, NAPI_AUTO_LENGTH, &world);
assert(status == napi_ok); // 运行结果断言
return world;
}
/** 对象属性的描述符,这条宏命令最终被解释为下面结构体:
* typedef struct {
* // utf8name or name 应该至少有一个为空.
* const char* utf8name; // name
* napi_value name; // 0:NULL
*
* napi_callback method; // func 函数的指针
* napi_callback getter; // 0:NULL
* napi_callback setter; // 0:NULL
* napi_value value; // 0:NULL
*
* napi_property_attributes attributes; // 对应默认、可写、可枚举、可配置的属性
* void* data; // 0:NULL
*} napi_property_descriptor;
*/
#define DECLARE_NAPI_METHOD(name, func) \
{ name, 0, func, 0, 0, 0, napi_default, 0 }
// 一个普通函数描述符的结构体
/**
* 初始化函数。需要接收exports 对象,进行修改后再返回
*
* 定义JS 对象属性:
* napi_define_properties(napi_env env, //
* napi_value object, // 被操作的对象
* size_t property_count, // 属性(数组内元素)数量
* const napi_property_descriptor* properties);
* // 属性描述符(数组)的指针
* 其实这里对应的是一个属性数组与其内部元素的数量。
*/
static napi_value Init(napi_env env, napi_value exports) {
napi_status status;
napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method);
status = napi_define_properties(env, exports, 1, &desc);
assert(status == napi_ok);
return exports;
}
// 最终注册插件的宏命令,一般保持默认即可
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
typedef enum {
napi_ok,
napi_invalid_arg,
napi_object_expected,
napi_string_expected,
napi_name_expected,
napi_function_expected,
napi_number_expected,
napi_boolean_expected,
napi_array_expected,
napi_generic_failure,
napi_pending_exception,
napi_cancelled,
napi_escape_called_twice,
napi_handle_scope_mismatch,
napi_callback_scope_mismatch,
napi_queue_full,
napi_closing,
napi_bigint_expected,
napi_date_expected,
napi_arraybuffer_expected,
napi_detachable_arraybuffer_expected,
napi_would_deadlock // unused
} napi_status;
一般数据
好像我们并不能看到N-api
中关于数据的具体定义,但是这并不影响我们可以通过一系列的函数来构造或者解析JS
数据。虽然下面的代码看起来很多很吓人,但无外乎数据的获取、转换、设置,在JS 中,函数也是一种数据。
一般在调用napi
函数时,返回值都是napi_status
类型,所以需要预先声明一个napi_vlaue
用来存储函数返回值,具体操作为在调用函数时将传入返回值的指针
::: details 数据处理的方法
// 经过实验得知:
// napi 插件开发是基于32 位的,也就是说_WIN64 下定义的类型是不适用的
// 0xffffffffui32
#define NAPI_AUTO_LENGTH SIZE_MAX
// 获取更详细的运行数据
napi_get_last_error_info(napi_env env, const napi_extended_error_info** result);
// Getters for defined singletons/个人理解为获取预定义的数据类型
NAPI_EXTERN napi_status napi_get_undefined(napi_env env, napi_value* result);
NAPI_EXTERN napi_status napi_get_null(napi_env env, napi_value* result);
NAPI_EXTERN napi_status napi_get_global(napi_env env, napi_value* result);
NAPI_EXTERN napi_status napi_get_boolean(napi_env env, bool value, napi_value* result);
// 创建原始对象或方法。一般来说是需要传入具体数据的,如果没有传入的话,默认就是空对象
NAPI_EXTERN napi_status napi_create_object(napi_env env, napi_value* result);
NAPI_EXTERN napi_status napi_create_array(napi_env env, napi_value* result);
NAPI_EXTERN napi_status napi_create_array_with_length(napi_env env, size_t length, napi_value* result);
NAPI_EXTERN napi_status napi_create_double(napi_env env, double value, napi_value* result);
NAPI_EXTERN napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result);
NAPI_EXTERN napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result);
NAPI_EXTERN napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result);
NAPI_EXTERN napi_status napi_create_string_latin1(napi_env env, const char* str, size_t length, napi_value* result);
NAPI_EXTERN napi_status napi_create_string_utf8(napi_env env, const char* str, size_t length, napi_value* result);
NAPI_EXTERN napi_status napi_create_string_utf16(napi_env env, const char16_t* str, size_t length, napi_value* result);
NAPI_EXTERN napi_status napi_create_symbol(napi_env env, napi_value description, napi_value* result);
NAPI_EXTERN napi_status napi_create_function(napi_env env, const char* utf8name, size_t length, napi_callback cb, void* data, napi_value* result);
NAPI_EXTERN napi_status napi_create_error(napi_env env, napi_value code, napi_value msg, napi_value* result);
NAPI_EXTERN napi_status napi_create_type_error(napi_env env, napi_value code, napi_value msg, napi_value* result);
NAPI_EXTERN napi_status napi_create_range_error(napi_env env, napi_value code, napi_value msg, napi_value* result);
/**
* 获取原始数据类型的方法
* typedef enum {
* // ES6 types (corresponds to typeof)
* napi_undefined,
* napi_null,
* napi_boolean,
* napi_number,
* napi_string,
* napi_symbol,
* napi_object,
* napi_function,
* napi_external,
* napi_bigint,
*} napi_valuetype;
*/
NAPI_EXTERN napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result);
// 从JS 数据提取C 兼容的数据,也是要预先声明返回值的
NAPI_EXTERN napi_status napi_get_value_double(napi_env env, napi_value value, double* result);
NAPI_EXTERN napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result);
NAPI_EXTERN napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* result);
NAPI_EXTERN napi_status napi_get_value_int64(napi_env env, napi_value value, int64_t* result);
NAPI_EXTERN napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result);
// 字符串处理
// 拷贝???? 编码的字符串到缓冲区
NAPI_EXTERN napi_status napi_get_value_string_latin1(napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result);
NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result);
NAPI_EXTERN napi_status napi_get_value_string_utf16(napi_env env, napi_value value, char16_t* buf, size_t bufsize, size_t* result);
// 强制类型转换?
// Methods to coerce values
// These APIs may execute user scripts
NAPI_EXTERN napi_status napi_coerce_to_bool(napi_env env, napi_value value, napi_value* result);
NAPI_EXTERN napi_status napi_coerce_to_number(napi_env env, napi_value value, napi_value* result);
NAPI_EXTERN napi_status napi_coerce_to_object(napi_env env, napi_value value, napi_value* result);
NAPI_EXTERN napi_status napi_coerce_to_string(napi_env env, napi_value value, napi_value* result);
// 操作对象的方法
NAPI_EXTERN napi_status napi_get_prototype(napi_env env, napi_value object, napi_value* result);
NAPI_EXTERN napi_status napi_get_property_names(napi_env env, napi_value object, napi_value* result);
NAPI_EXTERN napi_status napi_set_property(napi_env env, napi_value object, napi_value key, napi_value value);
NAPI_EXTERN napi_status napi_has_property(napi_env env, napi_value object, napi_value key, bool* result);
NAPI_EXTERN napi_status napi_get_property(napi_env env, napi_value object, napi_value key, napi_value* result);
NAPI_EXTERN napi_status napi_delete_property(napi_env env, napi_value object, napi_value key, bool* result);
NAPI_EXTERN napi_status napi_has_own_property(napi_env env, napi_value object, napi_value key, bool* result);
NAPI_EXTERN napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, napi_value value);
NAPI_EXTERN napi_status napi_has_named_property(napi_env env, napi_value object, const char* utf8name, bool* result);
NAPI_EXTERN napi_status napi_get_named_property(napi_env env, napi_value object, const char* utf8name, napi_value* result);
NAPI_EXTERN napi_status napi_set_element(napi_env env, napi_value object, uint32_t index, napi_value value);
NAPI_EXTERN napi_status napi_has_element(napi_env env, napi_value object, uint32_t index, bool* result);
NAPI_EXTERN napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value* result);
NAPI_EXTERN napi_status napi_delete_element(napi_env env, napi_value object, uint32_t index, bool* result);
// 定义对象属性
napi_define_properties(napi_env env, napi_value object, size_t property_count, const napi_property_descriptor* properties);
// 操作数组的方法
NAPI_EXTERN napi_status napi_is_array(napi_env env, napi_value value, bool* result);
NAPI_EXTERN napi_status napi_get_array_length(napi_env env, napi_value value, uint32_t* result);
// 数据比较的方法
NAPI_EXTERN napi_status napi_strict_equals(napi_env env, napi_value lhs, napi_value rhs, bool* result);
// 操作函数的方法
NAPI_EXTERN napi_status napi_call_function(napi_env env, napi_value recv, napi_value func, size_t argc, const napi_value* argv, napi_value* result);
NAPI_EXTERN napi_status napi_new_instance(napi_env env, napi_value constructor, size_t argc, const napi_value* argv, napi_value* result);
NAPI_EXTERN napi_status napi_instanceof(napi_env env, napi_value object, napi_value constructor, bool* result);
NAPI_EXTERN napi_status napi_get_new_target(napi_env env, napi_callback_info cbinfo, napi_value* result);
NAPI_EXTERN napi_status napi_define_class(napi_env env, const char* utf8name, size_t length, napi_callback constructor, void* data, size_t property_count, const napi_property_descriptor* properties, napi_value* result);
// 封装扩展数据类型
NAPI_EXTERN napi_status napi_wrap(napi_env env, napi_value js_object, void* native_object, napi_finalize finalize_cb, void* finalize_hint, napi_ref* result);
NAPI_EXTERN napi_status napi_unwrap(napi_env env, napi_value js_object, void** result);
NAPI_EXTERN napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result);
NAPI_EXTERN napi_status napi_create_external(napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint, napi_value* result);
NAPI_EXTERN napi_status napi_get_value_external(napi_env env, napi_value value, void** result);
// 控制对象生命周期
// 这里的操作要比较小心,目测会有内存泄漏的风险
// 设置一个对象的引用
// Set initial_refcount to 0 for a weak reference, >0 for a strong reference.
NAPI_EXTERN napi_status napi_create_reference(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref* result);
// 删除对象的引用
// Deletes a reference. The referenced value is released, and may
// be GC'd unless there are other references to it.
NAPI_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref);
// Increments the reference count, optionally returning the resulting count.
// After this call the reference will be a strong reference because its
// refcount is >0, and the referenced object is effectively "pinned".
// Calling this when the refcount is 0 and the object is unavailable
// results in an error.
NAPI_EXTERN napi_status napi_reference_ref(napi_env env, napi_ref ref, uint32_t* result);
// Decrements the reference count, optionally returning the resulting count.
// If the result is 0 the reference is now weak and the object may be GC'd
// at any time if there are no other references. Calling this when the
// refcount is already 0 results in an error.
NAPI_EXTERN napi_status napi_reference_unref(napi_env env, napi_ref ref, uint32_t* result);
// Attempts to get a referenced value. If the reference is weak,
// the value might no longer be available, in that case the call
// is still successful but the result is NULL.
NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value* result);
NAPI_EXTERN napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result);
NAPI_EXTERN napi_status napi_close_handle_scope(napi_env env, napi_handle_scope scope);
NAPI_EXTERN napi_status napi_open_escapable_handle_scope(napi_env env, napi_escapable_handle_scope* result);
NAPI_EXTERN napi_status napi_close_escapable_handle_scope(napi_env env, napi_escapable_handle_scope scope);
NAPI_EXTERN napi_status napi_escape_handle(napi_env env, napi_escapable_handle_scope scope, napi_value escapee, napi_value* result);
// 异常处理
NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error);
NAPI_EXTERN napi_status napi_throw_error(napi_env env, const char* code, const char* msg);
NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, const char* code, const char* msg);
NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, const char* code, const char* msg);
NAPI_EXTERN napi_status napi_is_error(napi_env env, napi_value value, bool* result);
// 异常捕获
NAPI_EXTERN napi_status napi_is_exception_pending(napi_env env, bool* result);
NAPI_EXTERN napi_status napi_get_and_clear_last_exception(napi_env env, napi_value* result);
// 数组Buffer 和确定类型的数组
NAPI_EXTERN napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result);
NAPI_EXTERN napi_status napi_create_arraybuffer(napi_env env, size_t byte_length, void** data, napi_value* result);
NAPI_EXTERN napi_status napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, napi_finalize finalize_cb, void* finalize_hint, napi_value* result);
NAPI_EXTERN napi_status napi_get_arraybuffer_info(napi_env env, napi_value arraybuffer, void** data, size_t* byte_length);
NAPI_EXTERN napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result);
NAPI_EXTERN napi_status napi_create_typedarray(napi_env env, napi_typedarray_type type, size_t length, napi_value arraybuffer, size_t byte_offset, napi_value* result);
NAPI_EXTERN napi_status napi_get_typedarray_info(napi_env env, napi_value typedarray, napi_typedarray_type* type, size_t* length, void** data, napi_value* arraybuffer, size_t* byte_offset);
NAPI_EXTERN napi_status napi_create_dataview(napi_env env, size_t length, napi_value arraybuffer, size_t byte_offset, napi_value* result);
NAPI_EXTERN napi_status napi_is_dataview(napi_env env, napi_value value, bool* result);
NAPI_EXTERN napi_status napi_get_dataview_info(napi_env env, napi_value dataview, size_t* bytelength, void** data, napi_value* arraybuffer, size_t* byte_offset);
// Node 版本管理
NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result);
// Promises,异步
NAPI_EXTERN napi_status napi_create_promise(napi_env env, napi_deferred* deferred, napi_value* promise);
NAPI_EXTERN napi_status napi_resolve_deferred(napi_env env, napi_deferred deferred, napi_value resolution);
NAPI_EXTERN napi_status napi_reject_deferred(napi_env env, napi_deferred deferred, napi_value rejection);
NAPI_EXTERN napi_status napi_is_promise(napi_env env, napi_value value, bool* is_promise);
// Running a script,执行脚本,猜测是evl
NAPI_EXTERN napi_status napi_run_script(napi_env env, napi_value script, napi_value* result);
// 内存管理
NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, int64_t change_in_bytes, int64_t* adjusted_value);
// Dates
NAPI_EXTERN napi_status napi_create_date(napi_env env, double time, napi_value* result);
NAPI_EXTERN napi_status napi_is_date(napi_env env, napi_value value, bool* is_date);
NAPI_EXTERN napi_status napi_get_date_value(napi_env env, napi_value value, double* result);
// Add finalizer for pointer
NAPI_EXTERN napi_status napi_add_finalizer(napi_env env, napi_value js_object, void* native_object, napi_finalize finalize_cb, void* finalize_hint, napi_ref* result);
// BigInt
NAPI_EXTERN napi_status napi_create_bigint_int64(napi_env env, int64_t value, napi_value* result);
NAPI_EXTERN napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result);
NAPI_EXTERN napi_status napi_create_bigint_words(napi_env env, int sign_bit, size_t word_count, const uint64_t* words, napi_value* result);
NAPI_EXTERN napi_status napi_get_value_bigint_int64(napi_env env, napi_value value, int64_t* result, bool* lossless);
NAPI_EXTERN napi_status napi_get_value_bigint_uint64(napi_env env, napi_value value, uint64_t* result, bool* lossless);
NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env, napi_value value, int* sign_bit, size_t* word_count, uint64_t* words);
// Object
napi_get_all_property_names(napi_env env, napi_value object, napi_key_collection_mode key_mode, napi_key_filter key_filter, napi_key_conversion key_conversion, napi_value* result);
// Instance data
NAPI_EXTERN napi_status napi_set_instance_data(napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint);
NAPI_EXTERN napi_status napi_get_instance_data(napi_env env, void** data);
// ArrayBuffer detaching
NAPI_EXTERN napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer);
NAPI_EXTERN napi_status napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result);
// Type tagging,类型标签
NAPI_EXTERN napi_status napi_type_tag_object(napi_env env, napi_value value, const napi_type_tag* type_tag);
napi_check_object_type_tag(napi_env env, napi_value value, const napi_type_tag* type_tag, bool* result);
NAPI_EXTERN napi_status napi_object_freeze(napi_env env, napi_value object);
NAPI_EXTERN napi_status napi_object_seal(napi_env env, napi_value object);
:::
具体细节
获取函数的参数
传入函数的参数被存放在napi_callback_info
中,可以通过napi_get_cb_info
函数来提取。需要注意的是,在自定义插件中定义的函数,参数数量一般来说最好与JS
约定好。
/**
* // 一次调用获取所有回调函数的信息。(Ugly, but faster.)
* NAPI_EXTERN napi_status napi_get_cb_info(
* napi_env env, // [in] 运行环境
* napi_callback_info cbinfo, // [in] 回调函数的参数
* size_t* argc, // [in-out] 预设置取参数的数量
* // 返回实际取到的参数数量
* napi_value* argv, // [out] 参数数组
* napi_value* this_arg, // [out] 获取函数调用时this 对象
* void** data); // [out] 获取该函数的数据指针(不太懂
*/
static napi_value Add(napi_env env, napi_callback_info info) {
napi_status status;
// 1. 从napi_callback_info 提取参数及其数量
size_t argc = 2;
napi_value args[2];
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
assert(status == napi_ok);
if (argc < 2) {
// 抛出异常
napi_throw_type_error(env, NULL, "Wrong number of arguments");
return NULL;
}
// 2. 获取参数类型
napi_valuetype valuetype0;
status = napi_typeof(env, args[0], &valuetype0);
assert(status == napi_ok);
napi_valuetype valuetype1;
status = napi_typeof(env, args[1], &valuetype1);
assert(status == napi_ok);
// 3. 判断参数类型
if (valuetype0 != napi_number || valuetype1 != napi_number) {
napi_throw_type_error(env, NULL, "Wrong arguments");
return NULL;
}
// 4. 获取数据值
double value0;
status = napi_get_value_double(env, args[0], &value0);
assert(status == napi_ok);
double value1;
status = napi_get_value_double(env, args[1], &value1);
assert(status == napi_ok);
napi_value sum;
status = napi_create_double(env, value0 + value1, &sum);
assert(status == napi_ok);
return sum;
// 从上面的代码来看,获取函数参数的部分存在着大量的重复代码(对开发者而言),
// 当然这些代码在程序执行时都是必须的,但是考虑到开发效率,我们依然需要能提取
// 代码的冗余部分,进行封装。
}
调用(回调)函数
主要用来调用传入的JS 回调函数。
/**
* NAPI_EXTERN napi_status napi_call_function(napi_env env,
* napi_value recv, // this 对象
* // 非实例函数可以用napi_get_global() 获取global 对象
* napi_value func, // 函数体
* size_t argc, // 参数数量
* const napi_value* argv, // 传入参数
* napi_value* result); // 返回值
*/
static napi_value RunCallback(napi_env env, const napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value args[1];
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
assert(status == napi_ok);
napi_value cb = args[0];
napi_value argv[1];
status = napi_create_string_utf8(env, "hello world", NAPI_AUTO_LENGTH, argv);
assert(status == napi_ok);
napi_value global;
status = napi_get_global(env, &global);
assert(status == napi_ok);
napi_value result;
status = napi_call_function(env, global, cb, 1, argv, &result);
assert(status == napi_ok);
return NULL;
}
static napi_value Init(napi_env env, napi_value exports) {
napi_value new_exports;
napi_status status = napi_create_function(
env, "", NAPI_AUTO_LENGTH, RunCallback, NULL, &new_exports);
assert(status == napi_ok);
// 其实导出的对象也是一个普通的napi_value
return new_exports;
}
/**
* 创建函数
* napi_status napi_create_function(napi_env env, //
* const char* utf8name, // 函数名,留空表示匿名函数?
* size_t length, // 函数名长度,或者NAPI_AUTO_LENGTH
* napi_callback cb, // 函数体
* void* data, // 用户提供的数据上下文,调用时传递回函数。
* napi_value* result); // 返回值:返回一个函数类型
*/
对象工厂
创建JS 对象的方法。
/**
* NAPI_EXTERN napi_status napi_set_named_property(napi_env env, //
* napi_value object, //
* const char* utf8name, // 属性名
* napi_value value); // 属性值
*/
static napi_value CreateObject(napi_env env, const napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value args[1];
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
assert(status == napi_ok);
napi_value obj;
status = napi_create_object(env, &obj);
assert(status == napi_ok);
status = napi_set_named_property(env, obj, "msg", args[0]);
assert(status == napi_ok);
return obj;
}
// 导出为匿名函数
static napi_value Init(napi_env env, napi_value exports) {
napi_value new_exports;
napi_status status = napi_create_function(
env, "", NAPI_AUTO_LENGTH, CreateObject, NULL, &new_exports);
assert(status == napi_ok);
return new_exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
函数工厂
函数工厂就是构建JS 函数的方法,前文已经提到过了。
// 返回"hello world"
static napi_value MyFunction(napi_env env, napi_callback_info info) {
napi_status status;
napi_value str;
status = napi_create_string_utf8(env, "hello world", NAPI_AUTO_LENGTH, &str);
assert(status == napi_ok);
return str;
}
static napi_value CreateFunction(napi_env env, napi_callback_info info) {
napi_status status;
napi_value fn;
status = napi_create_function(
env, "theFunction", NAPI_AUTO_LENGTH, MyFunction, NULL, &fn);
assert(status == napi_ok);
return fn;
}
晋级C++
前面我们基本上已经了解了C
语言插件的开发,特点就是代码很长。于是我们可以采用面向对象的方式来封装代码。于是,以后将开始引入C++
,将JS 对象与CPP 对象一一对应起来。然鹅对于不熟悉C++
的我来说,看代码果然还是太难了些。
对象的封装
封装一个自定义的C++
对象类型,并将函数、属性、初始化的方法,统统写到对象内部。这样就可以只对外保留导出模块的接口了。似乎每一个模块都会有一个实例instance
。**而Constructor,New
则应该是约定好的,会被JS 调用的函数名。**看一遍源码只能说能了解个大概,真要是用还需要大量的练习。
#include "myobject.h"
napi_value Init(napi_env env, napi_value exports) {
return MyObject::Init(env, exports);
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
#ifndef TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_
#define TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_
#include <node_api.h>
class MyObject {
public:
static napi_value Init(napi_env env, napi_value exports);
static void Destructor(napi_env env, void* nativeObject, void* finalize_hint);
private:
// explicit 拒绝构造函数的隐式调用,总之能用就用
explicit MyObject(double value_ = 0);
~MyObject();
static napi_value New(napi_env env, napi_callback_info info);
static napi_value GetValue(napi_env env, napi_callback_info info);
static napi_value SetValue(napi_env env, napi_callback_info info);
static napi_value PlusOne(napi_env env, napi_callback_info info);
static napi_value Multiply(napi_env env, napi_callback_info info);
static inline napi_value Constructor(napi_env env);
double value_;
napi_env env_;
napi_ref wrapper_;
};
#endif // TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_
#include "myobject.h"
#include <assert.h>
// 多继承,对象派生自:value_(value), env_(nullptr), wrapper_(nullptr)
// 需要注意的是,对象本身的值存放于value_(value)
MyObject::MyObject(double value)
: value_(value), env_(nullptr), wrapper_(nullptr) {}
MyObject::~MyObject() {
// 删除JS 引用
napi_delete_reference(env_, wrapper_);
}
void MyObject::Destructor(napi_env env,
void* nativeObject,
void* /*finalize_hint*/) {
// reinterpret_cast 运算符并不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释
reinterpret_cast<MyObject*>(nativeObject)->~MyObject();
// 调用析构函数
}
// 定义属性描述符
#define DECLARE_NAPI_METHOD(name, func) \
{ name, 0, func, 0, 0, 0, napi_default, 0 }
// 用于初始化模块,并返回构造好的.node 模块
napi_value MyObject::Init(napi_env env, napi_value exports) {
napi_status status;
// 创建属性描述符
napi_property_descriptor properties[] = {
// 添加property->value,同时也包含getter,setter
{"value", 0, 0, GetValue, SetValue, 0, napi_default, 0},
// 对象包含两个方法
DECLARE_NAPI_METHOD("plusOne", PlusOne),
DECLARE_NAPI_METHOD("multiply", Multiply),
};
/**
* 定义JS 类
* napi_status napi_define_class(napi_env env,
* const char* utf8name, // 类名
* size_t length, // 类名长度
* napi_callback constructor, // 构造函数
* void* data, // 传入构造函数的data(可选)
* size_t property_count, // 类属性长度
* const napi_property_descriptor* properties,
* // 类属性数组
* napi_value* result); // 返回类对象
*/
napi_value cons;
status = napi_define_class(
env, "MyObject", NAPI_AUTO_LENGTH, New, nullptr, 3, properties, &cons);
assert(status == napi_ok);
// 因为插件的Init 之后,我们还需要调用此构造函数,所以
// 我们需要创建一个强引用,将其持久化存储。这样我们就能
// 在插件中任何位置,通过`napi_get_instance_data` 检
// 索到它。我们不能将其设置为全局的静态变量,因为这样的
// 话无法支持worker 线程以及单线程的多个上下文。
napi_ref* constructor = new napi_ref;
status = napi_create_reference(env, cons, 1, constructor); // 创建强引用
assert(status == napi_ok);
status = napi_set_instance_data( // 添加到实例
env, //
constructor, // void* data。存入的数据
// 卸载插件时会调用此lambda 函数,来释放强引用与堆内存
[](napi_env env, void* data, void* hint) {
napi_ref* constructor = static_cast<napi_ref*>(data);
napi_status status = napi_delete_reference(env, *constructor);
assert(status == napi_ok);
delete constructor;
}, // napi_finalize finalize_cb。卸载插件时的回调函数
nullptr); // 可选提示
assert(status == napi_ok);
status = napi_set_named_property(env, exports, "MyObject", cons);
assert(status == napi_ok);
return exports;
}
// 对应JS 中的构造函数
napi_value MyObject::Constructor(napi_env env) {
// 获取存放在示例中的构造函数的引用
void* instance_data = nullptr;
napi_status status = napi_get_instance_data(env, &instance_data);
assert(status == napi_ok);
napi_ref* constructor = static_cast<napi_ref*>(instance_data);
napi_value cons;
status = napi_get_reference_value(env, *constructor, &cons);
assert(status == napi_ok);
return cons;
}
napi_value MyObject::New(napi_env env, napi_callback_info info) {
napi_status status;
napi_value target;
status = napi_get_new_target(env, info, &target);
assert(status == napi_ok);
bool is_constructor = target != nullptr;
// new.target指向被new调用的构造函数,如果该方法不是构造函数,则会返回null
if (is_constructor) {
// 被当作构造函数调用
// Invoked as constructor: `new MyObject(...)`
size_t argc = 1;
napi_value args[1];
napi_value jsthis; // 构造函数应有this
status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr);
assert(status == napi_ok);
double value = 0;
napi_valuetype valuetype;
status = napi_typeof(env, args[0], &valuetype);
assert(status == napi_ok);
if (valuetype != napi_undefined) {
status = napi_get_value_double(env, args[0], &value);
assert(status == napi_ok);
}
MyObject* obj = new MyObject(value);
obj->env_ = env;
status = napi_wrap(env,
jsthis, // 将obj 包装成this
reinterpret_cast<void*>(obj), // 本地对象
MyObject::Destructor, // 准备垃圾回收时销毁本地对象
nullptr, // finalize_hint,可选提示
&obj->wrapper_); // 返回值
assert(status == napi_ok);
return jsthis;
} else {
// 被当作普通函数调用
// Invoked as plain function `MyObject(...)`, turn into construct call.
size_t argc_ = 1;
napi_value args[1];
status = napi_get_cb_info(env, info, &argc_, args, nullptr, nullptr);
assert(status == napi_ok);
const size_t argc = 1;
napi_value argv[argc] = {args[0]};
napi_value instance;
status = napi_new_instance(env, Constructor(env), argc, argv, &instance);
assert(status == napi_ok);
return instance;
}
}
// Getter、Setter
napi_value MyObject::GetValue(napi_env env, napi_callback_info info) {
napi_status status;
napi_value jsthis;
status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr);
assert(status == napi_ok);
MyObject* obj;
// 解除包装
status = napi_unwrap(env, jsthis, reinterpret_cast<void**>(&obj));
assert(status == napi_ok);
napi_value num;
status = napi_create_double(env, obj->value_, &num);
assert(status == napi_ok);
return num;
}
napi_value MyObject::SetValue(napi_env env, napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value value;
napi_value jsthis;
status = napi_get_cb_info(env, info, &argc, &value, &jsthis, nullptr);
assert(status == napi_ok);
MyObject* obj;
status = napi_unwrap(env, jsthis, reinterpret_cast<void**>(&obj));
assert(status == napi_ok);
// 虽然是get 函数,但是将传入的值get 到对象属性上面去了。。。
status = napi_get_value_double(env, value, &obj->value_);
assert(status == napi_ok);
return nullptr;
}
napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) {
napi_status status;
napi_value jsthis;
status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr);
assert(status == napi_ok);
MyObject* obj;
status = napi_unwrap(env, jsthis, reinterpret_cast<void**>(&obj));
assert(status == napi_ok);
obj->value_ += 1;
napi_value num;
status = napi_create_double(env, obj->value_, &num);
assert(status == napi_ok);
return num;
}
napi_value MyObject::Multiply(napi_env env, napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value args[1];
napi_value jsthis;
status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr);
assert(status == napi_ok);
napi_valuetype valuetype;
status = napi_typeof(env, args[0], &valuetype);
assert(status == napi_ok);
double multiple = 1;
if (valuetype != napi_undefined) {
status = napi_get_value_double(env, args[0], &multiple);
assert(status == napi_ok);
}
MyObject* obj;
status = napi_unwrap(env, jsthis, reinterpret_cast<void**>(&obj));
assert(status == napi_ok);
const int kArgCount = 1;
napi_value argv[kArgCount];
status = napi_create_double(env, obj->value_ * multiple, argv);
assert(status == napi_ok);
napi_value instance;
status = napi_new_instance(env, Constructor(env), kArgCount, argv, &instance);
assert(status == napi_ok);
return instance;
}
工厂封装
仍然以MyObject
为例,工厂的封装其实就相当于增加了一个调用对象构造函数的层。
#include <assert.h>
#include "myobject.h"
napi_value CreateObject(napi_env env, napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value args[1];
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
assert(status == napi_ok);
napi_value instance;
status = MyObject::NewInstance(env, args[0], &instance);
assert(status == napi_ok);
return instance;
}
napi_value Init(napi_env env, napi_value exports) {
// 仍然需要在加载插件时初始化MyObject 类
napi_status status = MyObject::Init(env);
assert(status == napi_ok);
napi_value new_exports;
status = napi_create_function(
env, "", NAPI_AUTO_LENGTH, CreateObject, nullptr, &new_exports);
assert(status == napi_ok);
return new_exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
Passing_Wraoed
说实话这一节我不知道该如何翻译,因为内容开起来与前面的东西没有什么不同。无非是增加了一个函数,可以将JS 对象转化为C++ 对象并进行处理。
#include <assert.h>
#include "myobject.h"
// 对象工厂
napi_value CreateObject(napi_env env, napi_callback_info info) {
napi_status status;
size_t argc = 1;
napi_value args[1];
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
assert(status == napi_ok);
napi_value instance;
status = MyObject::NewInstance(env, args[0], &instance);
return instance;
}
// 对象的加法函数
napi_value Add(napi_env env, napi_callback_info info) {
napi_status status;
size_t argc = 2;
napi_value args[2];
status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
assert(status == napi_ok);
// 类型转换
MyObject* obj1;
status = napi_unwrap(env, args[0], reinterpret_cast<void**>(&obj1));
assert(status == napi_ok);
MyObject* obj2;
status = napi_unwrap(env, args[1], reinterpret_cast<void**>(&obj2));
assert(status == napi_ok);
napi_value sum;
status = napi_create_double(env, obj1->Val() + obj2->Val(), &sum);
assert(status == napi_ok);
return sum;
}
#define DECLARE_NAPI_METHOD(name, func) \
{ name, 0, func, 0, 0, 0, napi_default, 0 }
napi_value Init(napi_env env, napi_value exports) {
napi_status status;
MyObject::Init(env);
napi_property_descriptor desc[] = {
DECLARE_NAPI_METHOD("createObject", CreateObject),
DECLARE_NAPI_METHOD("add", Add),
};
status =
napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc);
assert(status == napi_ok);
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
看到这里,我们就会发现,虽然可以使用C++ 来封装代码,但是工作量依然好大,能不能再简单一些,让我们可以直接写C++ 代码就行了。幸运的是,官方已经在维护这样的库了:node-addon-api。
node-addon-api
在理解了上述代码之后,就理清了n-api
插件的工作原理,基于node-addon-api
的开发也就水到渠成了。下面仅记录一下node-addon-api
的安装过程。
前提条件
- 已经安装了NodeJS
- 已经安装了node-gyp
安装和使用
- 在
package.json
中添加依赖项:// yarn add node-addon-api "dependencies": { "node-addon-api": "*", }
- 在
binding.gyp
中添加头文件引用'include_dirs': ["<!(node -p \"require('node-addon-api').include_dir\")"],
- 在
binding.gyp
中声明是否启用C++
异常功能。因为基本的C-API 不会抛出或者处理C++ 异常,而C++ 包装器却可以选择启用此功能'cflags!': [ '-fno-exceptions' ], 'cflags_cc!': [ '-fno-exceptions' ], 'xcode_settings': { 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'CLANG_CXX_LIBRARY': 'libc++', 'MACOSX_DEPLOYMENT_TARGET': '10.7', }, 'msvs_settings': { 'VCCLCompilerTool': { 'ExceptionHandling': 1 }, }, # 或者干脆禁用掉此功能 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
- 配置支持
OSX
'conditions': [ ['OS=="mac"', { 'cflags+': ['-fvisibility=hidden'], 'xcode_settings': { 'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden } }] ]
- 在项目代码中引入头文件
#include "napi.h"
在构建时,只有当目标节点版本没有内置 Node-API 时,才会使用 Node-API back-compat 库代码。
预处理器指令NODE_ADDON_API_DISABLE_DEPRECATED
可以在编译时定义,然后包含 napi.h
以跳过不推荐使用的API 的定义。
6. 导入插件,示例中采用node-bindings
库来导入编译好的文件。因为一般编译好的文件会存在于./Release/*
比较深层的目录,比较不好找。
创建第一个项目
一般我们采用框架来自动生成代码,这里用到了Yoman
和generator-napi-module
两个库
npm install -g yo
npm install -g generator-napi-module
# 创建项目文件夹
mkdir hello-world
cd hello-world
yo napi-module
# 项目配置选项
package name: (hello-world)
version: (1.0.0)
description: A first project.
git repository:
keywords:
author: Your name goes here
license: (ISC)
Yeoman will display the generated package.json file here.
Is this OK? (yes) yes
? Choose a template Hello World
? Would you like to generate TypeScript wrappers for your module? No
相关资料
- node-addon-examples
- C/C++ addons with Node-API
- node-addon-api
- Node-API Resource
- A first project:主要介绍如何构建一个N-API 项目