4. 样例工程源码剖析
4.1. NAPI 导出对象和生命周期管理具体实现
4.1.1. 定义 NapiTest 类及方法
#ifndef __NAPI_TEST_H__#define __NAPI_TEST_H__
#include "napi/native_api.h"#include <js_native_api_types.h>
#include <iostream>
#define NAPI_CLASS_NAME "NapiTestClass"
class NapiTest {public: NapiTest() : mEnv(nullptr), mRef(nullptr) { } NapiTest(napi_env env) : mEnv(env), mRef(nullptr){ } ~NapiTest(); // 创建NapiTest类的实体,并将实体返回到应用端,该方法为js创建一个类实体,因此需要将该接口对外导出 static napi_value Create(napi_env env, napi_callback_info info); // 初始化js类并设置对应属性并将其导出 static napi_value Init(napi_env env, napi_value exports);
private: // 设置数据,此方法给到js直接调用,因此需要将该接口对外导出 static napi_value SetMsg(napi_env env, napi_callback_info info); // 获取数据,此方法给到js直接调用,因此需要将该接口对外导出 static napi_value GetMsg(napi_env env, napi_callback_info info); // 定义js结构体时实际的构建函数 static napi_value Constructor(napi_env env, napi_callback_info info);
// 释放资源的函数(类似类的析构函数) static void Destructor(napi_env env, void *nativeObject, void *finalize);
// 生命周期变量 static napi_ref sConstructor_;
// 设置和获取数据的变量 static std::string _msg;
// 记录环境变量 napi_env mEnv = nullptr;
// 记录生命周期变量 napi_ref mRef = nullptr;
};
#endif /* __NAPI_TEST_H__ */
复制代码
4.1.1.1 napi_value
Node.js Node-API 的值用 napi_value 类型表示。OpenHarmony NAPI 将 ECMAScript 标准中定义的 Boolean、Null、Undefined、Number、BigInt、String、Symbol 和 Object 八种数据类型,以及函数对应的 Function 类型,统一封装成 napi_value 类型,下文中表述为 JS 类型,用于接收 ArkUI 应用传递过来的数据及返回数据给 ArkUI 应用。
这是一个不透明的指针,用于表示 JavaScript 值。
4.1.1.2 napi_ref
4.1.1.3 napi_env
4.1.2 将 NapiTest 类定义为 js 类
4.1.2.1 在定义 js 类之前,需要先设置 js 类对外导出的方法
// 在定义js类之前,需要先设置类对外导出的方法 napi_property_descriptor desc[] = { { "getMsg", nullptr, NapiTest::GetMsg, nullptr, nullptr, nullptr, napi_default, nullptr }, { "setMsg", nullptr, NapiTest::SetMsg, nullptr, nullptr, nullptr, napi_default, nullptr }, { "create", nullptr, NapiTest::Create, nullptr, nullptr, nullptr, napi_default, nullptr } };
复制代码
4.1.2.1.1 napi_property_descriptor
参考 https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_napi_property_descriptor
Node.js Node-API 有一组 API 来获取和设置 JavaScript 对象的属性。在 JavaScript 中,属性被表示为一个键和一个值的元组。基本上,Node-API 中的所有属性键都可以用以下形式中的任一一种表示:
typedef struct { // utf8name和name其中一个必须是NULL const char* utf8name; napi_value name;
napi_callback method; napi_callback getter; napi_callback setter; napi_value value;
napi_property_attributes attributes; void* data;} napi_property_descriptor;
复制代码
参数解析:
utf8name:在定义 js 类之前设置的 js 类对外导出的方法名字,编码为 UTF8。必须为该属性提供 utf8name 或 name 中的一个。(utf8name 和 name 其中一个必须是 NULL)
name:可选的 napi_value,指向一个 JavaScript 字符串或符号,用作属性的键。必须为该属性提供 utf8name 或 name 中的一个。
method:将属性描述符对象的 value 属性设置为 method 表示的 JavaScript 函数。如果传入这个参数,将 value、getter 和 setter 设置为 NULL(因为这些成员不会被使用)。
attributes:与特定属性相关联的属性。
data:调用函数时传递给 method、getter 和 setter 的 callback data。
4.1.2.2 定义与 C++类相对应的 JavaScript 类
napi_value constructor = nullptr;
// 定义与C++类相对应的JavaScript类 if (napi_define_class(env, NAPI_CLASS_NAME, NAPI_AUTO_LENGTH, Constructor, nullptr, sizeof(desc) / sizeof(desc[0]), desc, &constructor) != napi_ok) { // "!="用来检查两个操作数的值是否相等,如果不相等则条件为真 return nullptr; }
复制代码
4.1.2.2.1 napi_define_class
napi_define_class函数说明:https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_napi_define_class
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);
复制代码
功能:定义与 C ++ 类相对应的 JavaScript 类。参数说明:
[in] env: 调用 api 的环境
[in] utf8name: C ++ 类的名称
[in] length: C ++ 类的名称的长度,默认自动长度使用NAPI_AUTO_LENGTH
[in] constructor: 处理 C ++ 类实例构造的回调函数 (因为 Constructor 函数被 napi_define_class 调用了)。在导出 C ++ 类对象时,这个函数必须是带有 napi_callback 签名(Constructor 函数有 napi_callback 签名是指要满足 typedef napi_value (*napi_callback)(napi_env, napi_callback_info);的形式)的静态成员。不能使用 c ++ 的类构造函数。
[in] data: 作为回调信息的数据属性传递给构造函数回调的可选数据
[in] property_count: 属性数组中参数的个数
[in] properties: 属性数组,具体看代码中 napi_property_descriptor 部分
[out] result: 通过类构造函数绑定类实例的 napi_value 对象返回:如果 API 调用成功返回 napi_ok。
JS 构造函数如果一个 js 函数被使用 new 操作符来调用了,那么这个函数就称之为 js 构造函数
C++类回调函数我们调用别人的 API 叫 call,调用的第三方 API 调用我们的函数叫回调(callback)
4.1.2.3 实现 js 类的构造函数
当 ArkTS 应用在 js 端通过 new 方法获取类对象的时候,此时会调用 napi_define_class 中设置的 constructor 回调函数,该函数实现方法如下:
napi_value NapiTest::Constructor(napi_env env, napi_callback_info info){ napi_value undefineVar = nullptr, thisVar = nullptr; napi_get_undefined(env, &undefineVar);
// 获取传入的参数对象,对象不为空,根据该参数创建实例并并绑定到该对象 if (napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr) == napi_ok && thisVar != nullptr) {
// 创建NapiTest 实例 NapiTest *reference = new NapiTest(env);
// 绑定实例到对象并获取对象的生命周期 if (napi_wrap(env, thisVar, reinterpret_cast<void *>(reference), NapiTest::Destructor, nullptr, &(reference->mRef)) == napi_ok) { return thisVar; }
return thisVar; } return undefineVar;}
void NapiTest::Destructor(napi_env env, void *nativeObject, void *finalize){ // 释放资源 NapiTest *test = reinterpret_cast<NapiTest*>(nativeObject); test->~NapiTest();}
复制代码
void NapiTest::Destructor(napi_env env, void *nativeObject, void *finalize){ // 类析构函数,释放资源 NapiTest *test = reinterpret_cast<NapiTest*>(nativeObject); test->~NapiTest();}
复制代码
4.1.2.3.1 napi_wrap
napi_status napi_wrap(napi_env env, napi_value js_object, void* native_object, napi_finalize finalize_cb, void* finalize_hint, napi_ref* result);
复制代码
功能:将 C++类实例绑定到 js 对象,并关联对应的生命周期参数说明:
[in] env: 调用 api 的环境
[in] js_object: 绑定 native_object 的 js 对象
[in] native_object: C++类实例对象
[in] finalize_cb: 释放实例对象的回调函数
[in] finalize_hint: 传递给回调函数的数据
[out] result: 绑定 js 对象的引用
返回:调用成功返回 0,失败返回其他
4.1.2.3.2 napi_get_cb_info
NAPI 提供了 napi_get_cb_info()方法可从 napi_callback_info 中获取参数列表、this 及其他数据。这个方法在 constructor 回调函数中使用,从给定的回调信息中检索有关调用的详细信息,如参数和 This 指针。
napi_status napi_get_cb_info(napi_env env, napi_callback_info cbinfo, size_t* argc, napi_value* argv, napi_value* this_arg, void** data)
复制代码
参数说明:
[in] env: 传入接口调用者的环境,包含 js 引擎等,由框架提供,默认情况下直接传入即可
[in] cbinfo: napi_callback_info 对象,上下文的信息
[in-out] argc: argv 数组的长度。若 napi_callback_info 中实际包含的参数的个数大于请求的数量 argc,将只复制 argc 的值所指定数量的参数只 argv 中。若实际的参数个数小于请求的数量,将复制全部的参数,数组多余的空间用空值填充,并将参数实际长度写入 argc。
[out] argv: 用于接收参数列表
[out] this_arg: 用于接收 this 对象
[out] data: NAPI 的上下文数据 返回值:返回 napi_ok 表示转换成功,其他值失败。下面的返回 napi_status 方法一样。
4.1.3 导出 js 类
// 创建生命周期,初始引用计数设为1 if (napi_create_reference(env, constructor, 1, &sConstructor_) != napi_ok) { return nullptr; }
// 设置NapiTest对象相关属性并绑定到导出变量exports if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) != napi_ok) { return nullptr; }
复制代码
4.1.3.1 在设置 js 类导出前,需要先创建生命周期
if (napi_create_reference(env, constructor , 1, &sConstructor_) != napi_ok) { return nullptr;}
复制代码
4.1.3.1.1 napi_create_reference
napi_create_reference为对象创建一个 reference,以延长其生命周期。调用者需要自己管理 reference 生命周期。
napi_create_reference 函数说明:
NAPI_EXTERN napi_status napi_create_reference(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref* result);
复制代码
功能:通过引用对象创建新的生命周期引用对象
[in] env: 调用 API 的环境
[in] value: napi_value 表示我们要引用的对象
[in] initial_refcount: 生命周期变量的初始引用计数
[out] result: 新建的生命周期引用对象返回 napi_ok 这个 API 就是成功的.
4.1.3.2 将生命周期变量作为导出对象的传入属性,并将 js 类导出到 exports 中
// 设置constructor对象相关属性并绑定到导出变量exportsif (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) != napi_ok) { return nullptr;}
复制代码
4.1.3.2.1 napi_set_named_property
为给定对象的属性设置一个名称。
napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8Name, napi_value value);
复制代码
4.1.3.3 设置导出对象的属性
hello.cpp 中
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
复制代码
4.1.3.3.1 napi_define_properties
https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_napi_define_properties
napi_status napi_define_properties(napi_env env, napi_value object, size_t property_count, const napi_property_descriptor* properties);
复制代码
作用:批量的向给定 Object 中定义属性
4.1.4 创建类的实例对象
napi_value NapiTest::Create(napi_env env, napi_callback_info info) { napi_status status; napi_value constructor = nullptr, result = nullptr; // 获取生命周期变量 status = napi_get_reference_value(env, sConstructor_, &constructor);
// 创建生命周期内的实例对象并将其返回 status = napi_new_instance(env, constructor, 0, nullptr, &result); auto napiTest = new NapiTest(); // 绑定实例类创建NapiTest到导出的对象result if (napi_wrap(env, result, reinterpret_cast<void *>(napiTest), Destructor, nullptr, &(napiTest->mRef)) == napi_ok) { return result; } return nullptr;}
复制代码
4.1.4.1 napi_get_reference_value
https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_napi_get_reference_value
函数说明:
NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value* result);
复制代码
4.1.4.2 napi_new_instance
https://nodejs.org/docs/latest-v14.x/api/n-api.html#n_api_napi_new_instance
napi_status napi_new_instance(napi_env env, napi_value cons, size_t argc, napi_value* argv, napi_value* result)
复制代码
作用:通过给定的构造函数,构建一个对象
[in] env: 调用 API 的环境
[in] cons: napi_value 表示要作为构造函数调用的 JavaScript 函数
[in] argc: argv 数组中的元素计数
[in] argv: JavaScript 值数组,表示构造函数的参数 napi_value。
[out] result: napi_value 表示返回的 JavaScript 对象
4.2 index.d.ts 声明文件编写
使用 NAPI 框架代码生成工具,可以根据.h 生成.d.tshttps://gitee.com/openharmony/napi_generator/blob/master/docs/INSTRUCTION_ZH.md
export const create : () => NapiTest;export class NapiTest { setMsg(msg: string): void; getMsg(): string;}
复制代码
也可以写成
export class NapiTest { create(); setMsg(msg: string): void; getMsg(): string;}
复制代码
4.3 CMakeLists.txt 文件
# the minimum version of CMake.cmake_minimum_required(VERSION 3.4.1)project(ObjectWrapTest)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 头文件路径include_directories(${NATIVERENDER_ROOT_PATH} ${NATIVERENDER_ROOT_PATH}/include)# 动态库源文件add_library(entry SHARED hello.cpp NapiTest.cpp)# 依赖libace_napi.z.so动态库target_link_libraries(entry PUBLIC libace_napi.z.so )
复制代码
4.4 index.ets 文件
// 让IDE不检查文件语法// @ts-nocheck import testNapi from "libentry.so";
@Entry@Component
struct Index { @State message: string = '导出对象' @State nativePointer:number = 0
// 创建对象tt tt = testNapi.create();
build() { Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { console.info("[NapiTest] Test NAPI 2 + 3 = " + testNapi.add(2, 3)); try{ if (this.nativePointer == 0) { // log打印,在程序中添加 log console.info("[NapiTest] Test NAPI add(2, 3) 1"); this.nativePointer = testNapi.add(2, 3) console.info("[NapiTest] Test NAPI add(2, 3) 2");
this.tt.setMsg("2+3") console.info("[NapiTest] Test NAPI add(2, 3) 3");
} else {
console.info("[NapiTest] Test NAPI add(0, 0) 1");
this.nativePointer = testNapi.add(0, 0) console.info("[NapiTest] Test NAPI add(0, 0) 2");
this.tt.setMsg("4+5") console.info("[NapiTest] Test NAPI add(0, 0) 3"); } } catch(e) { console.info("[NapiTest]Test NAPI error" + JSON.stringify(e)); } console.info("[NapiTest]Test NAPI " + this.tt.getMsg() + " = " + this.nativePointer); }) } .width('100%') } .height('100%') }
}
复制代码
知识点附送
napi 接口名称
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/native-lib/third_party_napi/napi.md
评论