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

未完待续...

相关推荐
半盏茶香27 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Evand J1 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB1 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python
枫叶丹41 小时前
【HarmonyOS之旅】HarmonyOS开发基础知识(三)
华为od·华为·华为云·harmonyos
一个处女座的程序猿O(∩_∩)O1 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
Ronin3051 小时前
11.vector的介绍及模拟实现
开发语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
PieroPc3 小时前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
2401_857439696 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna6 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos