北向应用集成三方库——NAPI 导出类对象

简介

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类

  • 创建生命周期(生命周期相关可以参考文档napi生命周期)

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

    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即可。完整代码可以查看NapiTest源码

创建类的实例对象

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 
    

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习知识点,请移步前往小编:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容