OpenHarmony 入门——初识JS/ArkTS 侧的“JNI” NAPI 常见的函数详解(二)

引言

前面一篇文章OpenHarmony 入门------初识JS/ArkTS 侧的"JNI" NAPI(一)介绍了NAPI的基础理论知识,今天重点介绍下NAPI中重要的函数。

一、Native 侧的NAPI的相关的C++函数

以下面一段代码为例介绍下主要函数的功能和用法。

typescript 复制代码
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函数的定义过程,使得开发者不需要手动填写所有的结构体字段,只需提供函数名和实现即可。
c 复制代码
#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大同小异。

typescript 复制代码
#define DECLARE_NAPI_STATIC_FUNCTION(name, func)                                 \
    {                                                                            \
        (name), nullptr, (func), nullptr, nullptr, nullptr, napi_static, nullptr \
    }

选择使用 DECLARE_NAPI_STATIC_FUNCTION 还是 DECLARE_NAPI_FUNCTION 取决于你的函数是否需要与类的实例关联。如果你正在编写一个不需要类上下文的独立函数,应该使用静态函数。如果你正在实现一个类,并且函数是类的一部分,那么应该使用非静态函数。

typescript 复制代码
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 代码。

typescript 复制代码
#define DECLARE_NAPI_PROPERTY(name, val)\                                     
{                                                        \                                                        
(name), nullptr, nullptr, nullptr, nullptr, val, napi_default, nullptr \
}

使用 DECLARE_NAPI_PROPERTY 宏时,你需要提供以下信息:

  • 属性名称:这是在 JavaScript 代码中访问该属性时使用的名称。
  • 获取函数:一个可选的函数,当 JavaScript 代码尝试读取属性时被调用。
  • 设置函数:一个可选的函数,当 JavaScript 代码尝试设置属性的值时被调用。
  • 属性值:对于常量属性,这是属性的初始值
typescript 复制代码
#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 代码可以通过这个构造函数创建对象实例,并调用其方法和访问其属性。

typescript 复制代码
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 结构体用于描述类的属性和方法,其原型如下:

typescript 复制代码
typedef struct {
  const char* name;             // 属性或方法的名称
  napi_value getter;             // 获取器函数,如果属性是只读的,则设置为 NULL
  napi_value setter;             // 设置器函数,如果属性是只写的,则设置为 NULL
  napi_value method;            // 方法函数,如果属性不是方法,则设置为 NULL
  napi_value enum_value;        // 枚举值,通常设置为 NULL
  napi_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 函数的原型如下:

typescript 复制代码
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 的指针,成功创建引用后,该位置将存储新引用的句柄。
typescript 复制代码
#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 函数的原型如下:

typescript 复制代码
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。
typescript 复制代码
#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 函数的原型如下:

typescript 复制代码
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 结构体用于描述每个属性,其原型如下:
typescript 复制代码
typedef struct {
  const char* name;             // 属性或方法的名称
  napi_value getter;             // 获取器函数,如果属性是只读的,则设置为 NULL
  napi_value setter;             // 设置器函数,如果属性是只写的,则设置为 NULL
  napi_value method;            // 方法函数,如果属性不是方法,则设置为 NULL
  napi_value enum_value;        // 枚举值,通常设置为 NULL
  napi_value default_value;      // 默认值,通常设置为 NULL
} napi_property_descriptor;

MyObjectConstructor 函数创建了一个新的 JavaScript 函数 MyObject,并使用 napi_define_properties 定义了它的静态属性 staticProperty 和静态方法 staticMethod。MyObjectMethod 是对象的方法,而 MyStaticMethod 是静态方法。napi_define_properties 函数通常在模块初始化或构造函数中使用,用于定义类原型或对象的属性和方法。

typescript 复制代码
#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 函数的原型如下:

typescript 复制代码
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 的指针,用于接收异步操作的上下文信息。
typescript 复制代码
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 函数的原型如下:

typescript 复制代码
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 等
typescript 复制代码
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*

typescript 复制代码
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 函数的原型如下:

typescript 复制代码
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 的指针,用于接收注册操作的结果。如果注册成功,这个值将是模块的导出对象。

未完待续...

相关推荐
小码哥0688 分钟前
智能化招聘系统设计与实现-Java
开发语言·python
北山太湖10 分钟前
Matlab安装硬件支持包
开发语言·matlab
-睡到自然醒~34 分钟前
提升应用性能:Go中的同步与异步处理
开发语言·后端·golang
只吃不吃香菜35 分钟前
Go WebSocket 协程泄漏问题分析与解决方案
开发语言·websocket·golang
学Java的bb39 分钟前
MybatisPlus
java·开发语言·数据库
讓丄帝愛伱40 分钟前
Mybatis Log Free插件使用
java·开发语言·mybatis
心之伊始43 分钟前
Netty线程模型与Tomcat线程模型对比分析
java·开发语言
oh,huoyuyan1 小时前
如何在火语言中指定启动 Chrome 特定用户配置文件
前端·javascript·chrome
前端大聪明20021 小时前
single-spa原理解析
前端·javascript