引言
前面一篇文章OpenHarmony 入门——初识JS/ArkTS 侧的“JNI” NAPI(一)介绍了NAPI的基础理论知识,今天重点介绍下NAPI中重要的函数。
一、Native 侧的NAPI的相关的C++函数
以下面一段代码为例介绍下主要函数的功能和用法。
napi_value MSLiteModelNapi::Init(napi_env env, napi_value exports) {napi_property_descriptor properties[] = {DECLARE_NAPI_FUNCTION("getInputs", GetInputs),DECLARE_NAPI_FUNCTION("predict", PredictAsync)};napi_property_descriptor staticProperty[] = {DECLARE_NAPI_STATIC_FUNCTION("loadModelFromFile", LoadMSLiteModelFromFile),DECLARE_NAPI_PROPERTY("Format", CreateFormatObject(env)),DECLARE_NAPI_PROPERTY("DataType", CreateDataTypeObject(env)),DECLARE_NAPI_PROPERTY("ThreadAffinityMode", CreateThreadAffinityModeObject(env)),};napi_value constructor = nullptr;napi_status status = napi_define_class(env, CLASS_NAME.c_str(), NAPI_AUTO_LENGTH, Constructor, nullptr, sizeof(properties) / sizeof(properties[0]), properties, &constructor);status = napi_create_reference(env, constructor, REFERENCE_CREATION_COUNT, &constructor_);status = napi_set_named_property(env, exports, CLASS_NAME.c_str(), constructor);status = napi_define_properties(env, exports, sizeof(staticProperty) / sizeof(staticProperty[0]), staticProperty);return exports;
}
1、DECLARE_NAPI_FUNCTION
宏接收两个参数:
- name:是要定义的函数的名字。
- func:是对应的函数实现。
宏展开后,它创建了一个结构体(可能是napi_property_descriptor),该结构体包含了以下字段: - name:函数的名字。
- method:函数的实现。
其他一些字段被设置为nullptr或者默认值,这些可能涉及到函数的属性、 getter、setter、enumerator、data以及属性标志等。这个宏的主要作用是简化N-API函数的定义过程,使得开发者不需要手动填写所有的结构体字段,只需提供函数名和实现即可。
#define DECLARE_NAPI_FUNCTION(name, func) \{ \(name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, nullptr \}
func是一个 C++ 函数,它被声明为 NAPI 函数 “func”。然后,这个函数被添加到导出的对象中,这样 JavaScript 代码就可以通过 require 函数引入并调用它。
2、DECLARE_NAPI_STATIC_FUNCTION
DECLARE_NAPI_STATIC_FUNCTION 宏用于声明一个静态函数,可以直接通过模块名直接调用。该函数将被导出为 NAPI 函数,这样 JavaScript 代码就可以通过 require 函数引入并调用它。其他与DECLARE_NAPI_FUNCTION大同小异。
#define DECLARE_NAPI_STATIC_FUNCTION(name, func) \{ \(name), nullptr, (func), nullptr, nullptr, nullptr, napi_static, nullptr \}
选择使用 DECLARE_NAPI_STATIC_FUNCTION 还是 DECLARE_NAPI_FUNCTION 取决于你的函数是否需要与类的实例关联。如果你正在编写一个不需要类上下文的独立函数,应该使用静态函数。如果你正在实现一个类,并且函数是类的一部分,那么应该使用非静态函数。
napi_value MethodA(napi_env env, napi_callback_info info) {// 实现具体的函数逻辑// ...return nullptr;
}NAPI_EXPORT NAPI_INIT() {...// 声明 NAPI 函数DECLARE_NAPI_STATIC_FUNCTION("methodA", MethodA);...
}
3、DECLARE_NAPI_PROPERTY
宏在 NAPI(Node.js API)中用于声明一个属性,这个属性可以是静态的或者属于某个对象的实例。属性在 NAPI 中可以被视为只读或可写的数据成员,它们允许你将 C/C++ 中的变量或常量暴露给 JavaScript 代码。
#define DECLARE_NAPI_PROPERTY(name, val)\
{ \
(name), nullptr, nullptr, nullptr, nullptr, val, napi_default, nullptr \
}
使用 DECLARE_NAPI_PROPERTY 宏时,你需要提供以下信息:
- 属性名称:这是在 JavaScript 代码中访问该属性时使用的名称。
- 获取函数:一个可选的函数,当 JavaScript 代码尝试读取属性时被调用。
- 设置函数:一个可选的函数,当 JavaScript 代码尝试设置属性的值时被调用。
- 属性值:对于常量属性,这是属性的初始值
#include <napi.h>Napi::Value GetMyProperty(const Napi::CallbackInfo& info) {// 返回属性值return Napi::Value::From(info.Env(), 42);
}void SetMyProperty(const Napi::CallbackInfo& info, const Napi::Value& value) {// 处理属性值的设置// 这里可以添加代码来处理 value 参数
}Napi::Object Init(Napi::Env env, Napi::Object exports) {// 使用 DECLARE_NAPI_PROPERTY 宏声明属性DECLARE_NAPI_PROPERTY("MyProperty", GetMyProperty, SetMyProperty);// 将声明的属性添加到模块的导出对象中exports.Set("MyProperty", MyProperty);return exports;
}NODE_API_MODULE(myaddon, Init)
4、DECLARE_NAPI_STATIC_PROPERTY
DECLARE_NAPI_PROPERTY 和 DECLARE_NAPI_STATIC_PROPERTY 是两个用于声明属性的宏,它们之间的主要区别在于属性的生命周期和作用域,DECLARE_NAPI_PROPERTY 用于声明一个与对象实例关联的属性,通常是可读写的,并且通过对象的实例来访问和修改;DECLARE_NAPI_STATIC_PROPERTY 用于声明一个静态属性,它属于类或模块,而不是某个特定的实例。静态属性通常是只读的,或者具有相同的getter和setter函数,这些函数不会依赖于对象的状态。
5、napi_define_class
Node.js API (NAPI) 中的一个函数,用于在 Node.js 环境中定义一个新的 JavaScript 函数,该函数可以创建一个对象的实例。这个函数通常用于将 C/C++ 中的类封装为 Node.js 模块中的构造函数,使得 JavaScript 代码可以通过这个构造函数创建对象实例,并调用其方法和访问其属性。
napi_status napi_define_class(napi_env env,const char* class_name,size_t constructor_name_count,const napi_property_descriptor* property_descriptors,napi_value constructor_function,napi_value* constructor_name);
其中参数说明:
env:指向当前环境的指针。
class_name:新类名的字符串。
constructor_name_count:构造函数名称的数量,通常为 1。
property_descriptors:指向一个 napi_property_descriptor 数组的指针,该数组描述了类的属性和方法。
constructor_function:一个 napi_value,表示构造函数,当调用构造函数时,会调用指定的 JavaScript 函数。
constructor_name:一个 napi_value 数组,包含与 constructor_function 对应的名称。
napi_property_descriptor 结构体用于描述类的属性和方法,其原型如下:
typedef struct {const char* name; // 属性或方法的名称napi_value getter; // 获取器函数,如果属性是只读的,则设置为 NULLnapi_value setter; // 设置器函数,如果属性是只写的,则设置为 NULLnapi_value method; // 方法函数,如果属性不是方法,则设置为 NULLnapi_value enum_value; // 枚举值,通常设置为 NULLnapi_value default_value; // 默认值,通常设置为 NULL
} napi_property_descriptor;
OpenHarmony NAPI 提供了一种“包装”C ++类和实例的方法,以便 JS 应用可以调用类的构造函数和方法。
Node.js Node-API 中关于导出类对象的内容,参考链接 : https:// nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_object_wrap
NAPI 导出类对象流程
- 通过napi_define_class定义一个 JS 类
- 它包含了与 C++ 类对应的构造函数、静态属性、方法、实例属性和方法。
- 通过napi_wrap将 C++ 实例绑定在 JS 对象中
- 当 JS 代码调用构造函数时,构造函数回调会使用 napi_wrap 将一个新的 C++ 实例绑定在 JS 对象中,然后返回绑定的 C++ 实例。
- 通过napi_unwrap获取作为调用目标的 C++ 实例
- 当 JS 调用 C++ 类 的方法或属性时,会调用相应的 napi_callback C++ 函数。对于实例回调,napi_unwrap获取作为调用目标的 C++ 实例 。
6、napi_create_reference 和 napi_get_reference_value、napi_delete_reference
napi_create_reference 用于创建对 JavaScript 对象的引用。这个引用可以用于在后续的 NAPI 调用中保持对象的生命周期,即使在对象不再被 JavaScript 代码直接引用时也不会被垃圾回收。当你在 C/C++ 代码中使用 NAPI 与 JavaScript 代码交互时,你可能需要在多个地方使用同一个 JavaScript 对象。为了确保这个对象在这些地方都被有效引用,你可以使用 napi_create_reference 来创建一个持久化的引用。这个引用可以通过一个整数值(通常称为引用句柄)来标识和操作。
napi_create_reference 函数的原型如下:
napi_status napi_create_reference(napi_env env, napi_value object, uint32_t initial_refcount, napi_ref* result);
参数说明:
- env:指向当前环境的指针。
- object:要创建引用的 JavaScript 对象。
- initial_refcount:引用的初始引用计数。通常设置为 1,表示创建一个新引用。
- result:指向 napi_ref 的指针,成功创建引用后,该位置将存储新引用的句柄。
#include <napi.h>napi_value CreateObject(napi_env env, napi_callback_info info) {// 创建一个新的 JavaScript 对象napi_value object;napi_create_object(env, &object);// 创建一个持久化的引用napi_ref ref;napi_create_reference(env, object, 1, &ref);// 这里可以将 ref 用于后续的调用,以便在需要时重新获取对象// 返回创建的对象return object;
}napi_value UseObjectReference(napi_env env, napi_callback_info info) {// 假设我们有一个有效的引用句柄napi_ref ref;// 从引用句柄获取对象napi_value object;napi_get_reference_value(env, ref, &object);// 使用对象...return nullptr;
}
使用 napi_create_reference 和 napi_get_reference_value 可以在 NAPI 函数之间共享和持久化 JavaScript 对象的引用,这在处理异步操作或跨多个调用共享资源时非常有用。需要注意的是,创建引用后,你需要负责管理引用的生命周期,包括在适当的时候使用 napi_delete_reference 来删除引用,以避免内存泄漏。
- napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
- napi_delete_reference() :删除napi_ref引用对象
7、napi_set_named_property
用于在 JavaScript 对象上设置一个命名属性的值。napi_set_named_property 函数的原型如下:
napi_status napi_set_named_property(napi_env env,napi_value object,const char16_t* property_name,napi_value value);
参数说明:
- env:指向当前 NAPI 环境的指针。
- object:要设置属性的 JavaScript 对象。
- property_name:一个以 UTF-16 编码的字符串,表示要设置的属性名。
- value:要设置的属性值的 napi_value。
#include <napi.h>napi_value SetObjectProperty(napi_env env, napi_callback_info info) {// 创建一个 JavaScript 对象napi_value obj;napi_create_object(env, &obj);// 创建一个属性值napi_value propValue;napi_create_string_utf8(env, "Hello, World!", NAPI_OK, &propValue);// 设置对象的属性const char16_t* propertyName = "exampleProperty";napi_status status = napi_set_named_property(env, obj, propertyName, propValue);if (status != napi_ok) {// 处理错误return nullptr;}// 返回创建的对象return obj;
}
SetObjectProperty 函数首先创建了一个空的 JavaScript 对象,然后创建了一个字符串值作为属性值。最后,使用 napi_set_named_property 函数将这个值设置为对象的 exampleProperty 属性。napi_set_named_property 是 NAPI 中用于对象属性操作的重要函数之一,它使得 C/C++ 代码能够动态地修改 JavaScript 对象的状态。
8、napi_define_properties
用于在构造函数初始化时定义一个对象的属性和方法。这个函数允许你一次性定义多个属性,而不是在运行时逐个设置。
napi_define_properties 函数的原型如下:
napi_status napi_define_properties(napi_env env,napi_value object,size_t property_count,const napi_property_descriptor* properties);
参数说明:
- env:指向当前 NAPI 环境的指针。
- object:要定义属性的 JavaScript 对象。
- property_count:要定义的属性数量。
- properties:指向一个 napi_property_descriptor 数组的指针,该数组描述了要定义的属性。
- napi_property_descriptor 结构体用于描述每个属性,其原型如下:
typedef struct {const char* name; // 属性或方法的名称napi_value getter; // 获取器函数,如果属性是只读的,则设置为 NULLnapi_value setter; // 设置器函数,如果属性是只写的,则设置为 NULLnapi_value method; // 方法函数,如果属性不是方法,则设置为 NULLnapi_value enum_value; // 枚举值,通常设置为 NULLnapi_value default_value; // 默认值,通常设置为 NULL
} napi_property_descriptor;
MyObjectConstructor 函数创建了一个新的 JavaScript 函数 MyObject,并使用 napi_define_properties 定义了它的静态属性 staticProperty 和静态方法 staticMethod。MyObjectMethod 是对象的方法,而 MyStaticMethod 是静态方法。napi_define_properties 函数通常在模块初始化或构造函数中使用,用于定义类原型或对象的属性和方法。
#include <napi.h>
napi_value MyObjectConstructor(napi_env env, napi_callback_info info) {// 创建一个新的 JavaScript 对象napi_value cons;napi_create_function(env, "MyObject", 0, MyObjectMethod, NULL, &cons);// 定义属性和方法napi_property_descriptor properties[] = {{"staticProperty", NULL, NULL, NULL, NULL, NULL},{"staticMethod", NULL, NULL, MyStaticMethod, NULL, NULL}};// 使用 napi_define_properties 定义属性和方法napi_status status = napi_define_properties(env, cons, sizeof(properties) / sizeof(properties[0]), properties);if (status != napi_ok) {// 处理错误return nullptr;}return cons;
}napi_value MyObjectMethod(napi_env env, napi_callback_info info) {// 实现对象的方法// ...return nullptr;
}napi_value MyStaticMethod(napi_env env, napi_callback_info info) {// 实现静态方法// ...return nullptr;
}
napi_define_properties 和 napi_set_named_property 都是 Node.js API (NAPI) 中用于操作 JavaScript 对象属性的函数。而napi_define_properties 通常在构造函数或模块初始化时使用,用于定义对象的多个属性和方法。接受一个属性描述符数组,可以一次性定义多个属性。使用 napi_define_properties 定义的属性会成为对象的原型属性,这意味着它们会被对象及其所有实例继承。它是在对象创建时调用的,用于设置对象的初始属性和方法;而napi_set_named_property 用于在运行时动态地设置或修改 JavaScript 对象的单个属性。接受对象、属性名和属性值作为参数;使用 napi_set_named_property 设置的属性是直接属性,它不会成为对象原型的一部分,也不会被对象的实例继承。它可以在任何时候调用,用于更新或添加对象的属性。
9、napi_get_cb_info从napi_callback_info类型的参数中得到回调的参数等数据
用于获取传递给异步回调或定时器回调的附加数据,通常在异步操作的完成回调中使用,以便访问在异步操作开始时附加到回调中的 NAPI 回调信息。napi_get_cb_info 函数的原型如下:
napi_status napi_get_cb_info(napi_env env,napi_callback_info info,size_t* argc,napi_value* argv,napi_value* this_arg,napi_async_context* async_context);
参数说明:
- env:指向当前 NAPI 环境的指针。
- info:传递给回调的 napi_callback_info。
- argc:指向一个 size_t 的指针,用于接收回调中的参数数量。
- argv:指向一个 napi_value 数组的指针,该数组将被填充回调中的参数。
- this_arg:指向一个 napi_value 的指针,用于接收回调中的 this 指针。
- async_context:指向一个 napi_async_context 的指针,用于接收异步操作的上下文信息。
napi_value MyAsyncCallback(napi_env env, napi_callback_info info) {// 获取回调信息size_t argc;napi_value argv[1];napi_value this_arg;napi_async_context async_context;// 使用 napi_get_cb_info 获取回调的参数和上下文napi_status status = napi_get_cb_info(env, info, &argc, argv, &this_arg, &async_context);if (status != napi_ok) {// 处理错误return nullptr;}// 使用 argc, argv, this_arg 和 async_context 进行后续操作// ...return nullptr;
}
napi_callback_info 中取出 JS 调用时传入的所有参数,并调用 napi 方法将其从 napi 数据类型的值 napi_value 转换成 native 数据类型的值argc为传入参数个数,argv为传入参数数组,此时参数的类型都为napi_value。
10、napi_typeof函数获取入参的实际类型
用于获取给定 napi_value 的类型信息,napi_typeof 函数的原型如下:
napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result);
- env:指向当前 NAPI 环境的指针。
- value:要检查类型的 napi_value。
- result:指向 napi_valuetype 变量的指针,该变量将接收表示 value 类型的枚举值。
napi_valuetype 枚举包含了多种类型,例如 napi_undefined、napi_null、napi_boolean、napi_number、napi_string、napi_symbol、napi_object、napi_function 等
napi_value CheckValueType(napi_env env, napi_callback_info info) {// 假设我们有一个 napi_value 需要检查类型napi_value value;napi_get_cb_info(env, info, NULL, &value, NULL, NULL);// 检查值的类型napi_valuetype type;napi_typeof(env, value, &type);// 根据类型进行不同的操作switch (type) {case napi_undefined:// 处理未定义类型的值break;case napi_null:// 处理空类型的值break;case napi_number:// 处理数字类型的值break;case napi_string:// 处理字符串类型的值break;// 其他类型的处理...default:// 未知类型的处理break;}return nullptr;
}
11、napi_get_value_string_utf8方法将napi_string转换char*
napi_status napi_get_value_string_utf8(napi_env env,napi_value value,char* buf,size_t bufsize,size_t* result);
12、 napi_module_register
用于注册一个 NAPI 模块。这个函数在模块的初始化代码中被调用,以便将模块的导出函数和属性添加到 Node.js 的全局对象上,从而使得这些函数和属性可以被 JavaScript 代码访问和使用。
napi_module_register 函数的原型如下:
napi_status napi_module_register(napi_env env,napi_object reference,napi_value (*module_init)(napi_env, napi_value),napi_value* result);
参数说明:
- env:指向当前 NAPI 环境的指针。
- reference:一个 napi_object,包含了模块的导出对象。这个对象通常是通过调用 napi_create_object 创建的,并且可以包含多个属性和方法。
- module_init:指向一个函数的指针,该函数用于初始化模块。这个函数将被调用,并且应该返回模块的导出对象。
- result:指向一个 napi_value 的指针,用于接收注册操作的结果。如果注册成功,这个值将是模块的导出对象。
未完待续…