简介

js调用napi的数据,对于简单的数据类型,只需要napi返回对应类型的napi_value数据即可 (详情参照napi数据类型类型与同步调用)。但是对于一些复杂的数据类型(如我们常用C++的类对象),是不能直接返回一个napi_value数据的。这时我们需要对这些数据进行一系列操作后将其导出,这样js才能使用导出后的对象。 本文以导出类对象为例来说明napi导出对象的具体过程。
类对象导出的具体过程:

NAPI导出类对象具体实现

这里我们以导出NapiTest类为例说明导出一个类的实现过程

定义NapiTest类以及相关方法

NapiTest类主要实现了接收js设置的数据并将该数据返回到js应用中,具体定义如下(NapiTest.h):

class NapiTest {
public:
  NapiTest() : mEnv(nullptr), mRef(nullptr) {
  }
  ~NapiTest();
  
  static napi_value Create(napi_env env, napi_callback_info info);  // 创建NapiTest类的实体,并将实体返回到应用端,该方法为js创建一个类实体,因此需要将该接口对外导出
  static napi_value Init(napi_env env, napi_value exports);         // 初始化js类并设置对应属性并将其导出。

private:
	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);     // 定义js结构体时实际的构建函数
    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;        // 记录生命周期变量
};

将NapiTest定义为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 },
}
  • 定义js类
napi_value mConstructor = nullptr;
if (napi_define_class(env, NAPI_CLASS_NAME, NAPI_AUTO_LENGTH, Constructor, nullptr,
    sizeof(desc) / sizeof(desc[0]), desc, &mConstructor) != napi_ok) {
    return nullptr;
}

使用到函数说明:

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++类定义为js的类
参数说明:

*   [in] env: 调用api的环境
*   [in] utf8name: C++类的名字
*   [in] length: C++类名字的长度,默认自动长度使用NAPI_AUTO_LENGTH
*   [in] constructor: 处理构造类实例的回调函数
*   [in] data: 作为回调信息的数据属性传递给构造函数回调的可选数据
*   [in] property_count: 属性数组参数中的个数
*   [in] properties: 属性数组
*   [out] result: 通过类构造函数绑定类实例的napi_value对象

返回:调用成功返回0,失败返回其他

  • 实现js类的构造函数

当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);
        // 绑定实例类创建NapiTest到导出的对象result
        if (napi_wrap(env, thisVar, reinterpret_cast<void *>(reference),
            NapiTest::Destructor, nullptr, &(reference->mRef)) == napi_ok) {
            return thisVar;
        }

        return thisVar;
    }
    
    return undefineVar;
}

其中NapiTest::Destructo方法是用来释放创建的对象:

void NapiTest::Destructor(napi_env env, void *nativeObject, void *finalize)
{
    NapiTest *test = reinterpret_cast<NapiTest*>(nativeObject);
    test->~NapiTest();
}

使用到函数说明:

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: 绑定C++类实例的js对象
*   [in] native_object: 类实例对象
*   [in] finalize_cb: 释放实例对象的回调函数
*   [in] finalize_hint: 传递给回调函数的数据
*   [out] result: 绑定js对象的引用

返回:调用成功返回0,失败返回其他

导出js类

在设置类导出前,需要先创建生命周期

if (napi_create_reference(env, mConstructor , 1, &sConstructor_) != napi_ok) {
    return nullptr;
}

mConstructor 定义js类时返回的代表类的构造函数的数据
sConstructor_ 生命周期变量

  • 将类导出到exports中 将类以属性值的方式导出
if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) !=  napi_ok) {
    return nullptr;
}

通过以上步骤,我们基本实现了NapiTest这个类的导出。
注意:以上实现都是在类的Init方法中,我们只需要在NAPI注册的接口中调用该Init即可。

创建类的实例对象

js应用除了调用new方法获取类的实例外,我们也可以提供一些方法让js应用获取对应的类的实例,如在我们的NapiTest类中,我们定义了一个Create方法,该方法实现了NapiTest类实例的获取。具体实现如下:

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;
}

在napi接口的注册中将该方法以接口的方式导出,应用层就可以直接调用该接口并获取到该类的实例对。
特别说明:如果单独实现了一个类实例获取的方法,那么js的类构造函数可以不实现。

实现NAPI接口的注册

我们已helloworld为列,

  • 新建一个hello.cpp,定义模块
static napi_module demoModule = {
    .nm_version =1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "hello",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};
  • 实现模块的Init
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
  napi_property_descriptor desc[] = {
      { "create", nullptr, NapiTest::Create, nullptr, nullptr, nullptr, napi_default, nullptr }   // 单独导出 create 方法,js应用可以直接调用Create方法获取类实例
  };
  
  napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);

  return NapiTest::Init(env, exports);    // 导出类以及类的方法
}
EXTERN_C_END
  • 模块注册
// 注册 hello模块
extern "C" __attribute__((constructor)) void RegisterHelloModule(void)
{
    napi_module_register(&demoModule);
}

至此,我们完成了整个napi接口注册以及napi类的导出。

应用调用NAPI实例

导出接口

在使用该NAPI的时候,我们需要在ts文件(路径在\entry\src\main\cpp\types\libentry\index.d.ts),声明以下内容:

export const create : () => NapiTest;
export class  NapiTest {
    setMsg(msg: string): void;
    getMsg(): string;
}

该文件申明了NAPI接口中导出的方法和类

###应用调用

新建一个helloworld的ETS工程,该工程中包含一个按键,我们可以通过该按键进行数据的在native C++中存储和获取

  • 导出napi对应的库(之前NAPI接口生成的库名为libentry.so)
import testNapi from "libentry.so";
  • 定义变量 tt
struct Index {
  @State message: string = 'Hello World'
  @State flag:number = 0
  tt = testNapi.create();

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
          })
      }
      .width('100%')
    }
    .height('100%')
  }
  • 在按键中调用对应的接口并输出内容
if (this.falg == 0) {
    this.flag = 2
    this.tt.setMsg("1+1")
} else {
    this.flag = 0
    this.tt.setMsg("1-1")
}
console.info("[NapiTest]:" + this.tt.getMsg() + " = " + this.flag);

通过IDE LOG信息可以查看到,当按多次下按钮时,出现交替以下信息:

02200/JsApp: [NapiTest]: 1+1 = 2
02200/JsApp: [NapiTest]: 1-1 = 0 

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐