北向应用集成三方库——NAPI生命周期

什么是NAPI的生命周期

我们都知道,程序的生命周期是指程序从启动,运行到最后的结束的整个过程。生命周期的管理自然是指控制程序的启动,调用以及结束的方法。而NAPI中的生命周期又是怎样的呢?如下图所示:

从图上我们可以看出,在js应用启动时会加载napi模块,而在napi模块加载过程中会创建一个napi对象A提供给应用使用,在应用退出或者主动释放A对象前,A对象必须一直保持"活跃"状态。从A对象创建到释放的整个过程也代表着A对象的生命周期。

NAPI生命周期管理的方法

js调用时,NAPI中对象的句柄可以作为napi_value返回. 这些句柄必须保持对象“活动”,直到本机代码不再需要它们,否则可以在本机代码完成使用它们之前回收对象。<br />
当返回对象句柄时,它们与“范围”相关联。默认范围的生命周期与本机方法调用的生命周期相关联。结果是,默认情况下,句柄保持有效,并且与这些句柄关联的对象将在本机方法调用的生命周期内保持活动状态。<br />
但是,在许多情况下,与本地方法相比,句柄必须在更短或更长的生命周期内保持有效。此时,NAPI提供了对应的函数来改变默认句柄的寿命(即生命周期)。

设置局部生命周期

因为在napi中全部js相关的值都是一个不透明的封装,默认生命周期是和全局一致的,有时候处于安全和性能的考虑,须要将一些值的生命周期限制在必定的范围之内,此时我们就需要用到NAPI相关的接口来napi_open_handle_scope和napi_close_handle_scope建立和关闭一个上下文环境。比如:

for (int i = 0; i < 1000000; i++) {
  napi_handle_scope scope;
  napi_status status = napi_open_handle_scope(env, &scope);
  if (status != napi_ok) {
    break;
  }
  napi_value result;
  status = napi_get_element(e object, i, &result);
  if (status != napi_ok) {
    break;
  }
  // do something with element
  status = napi_close_handle_scope(env, scope);
  if (status != napi_ok) {
    break;
  }
}

此时,因为限制了做用域,因此每一个result的生命周期都被限制在了单次循环以内。

使用到的函数:

napi_status napi_open_handle_scope(napi_env env, napi_handle_scope* result)

功能:打开一个局部的生命周期

参数说明:

  • [in] env - 当前环境变量
  • [out] result - 根据当前环境创建的生命周期变量

返回:napi_status,成功返回0,失败返回其他

napi_status napi_close_escapable_handle_scope(napi_env env, napi_handle_scope scope)

功能:关闭传入的生命周期(生命周期必须按照创建它们的相反顺序关闭)。

参数说明:

  • [in] env - 当前环境变量
  • [out] scope - 需要关闭的生命周期变量

返回:napi_status,成功返回0,失败返回其他

设置全局生命周期

在某些情况下,插件需要能够创建和引用具有比单个本地方法调用更长的生命周期的对象。例如,要创建一个构造函数并稍后在请求中使用该构造函数来创建实例,必须可以在许多不同的实例创建请求中引用该构造函数对象。如有一个napi_value变量constructor,需要将其导出到js使用:

{
  napi_value constructor = nullptr;
  ...
  if (napi_create_reference(env, constructor, 1, &sConstructor_) != napi_ok) {    // 创建生命周期,初始引用计数设为1
      return nullptr;
  }
  if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) != napi_ok) {   // 设置constructor对象相关属性并绑定到导出变量exports
      return nullptr;
  }
  ...
}

此时在其他线程或接口中就可以通过生命周期变量获取此constructor对象进行处理。

使用到的函数:

napi_status napi_create_reference(napi_env env, napi_value value, uint32_t initial_refcount, napi_ref *result)

功能:通过引用对象创建新的生命周期引用对象

参数:

  • [in] env: 当前环境变量
  • [in] value: 需要引用的对象
  • [in] initial_refcount: 引用计数初始值
  • [out] result: 新建的生命周期引用对象

返回:napi_status,成功返回0,失败其他

NAPI生命周期管理实现

这里我们以TestNapi为例(关于工程创建可以参照通过IDE开发一个Napi工程)

  • 首先新建一个 hello.cpp,实现 NAPI接口模块的注册

    #include "napi/native_api.h"
    #include <js_native_api_types.h>
    
    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports)
    {
     // 暂未实现任何方法
        napi_property_descriptor desc[] = {
        };
        return exports;
    }
    EXTERN_C_END
    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 },
    };
    // 注册 hello模块
    extern "C" __attribute__((constructor)) void RegisterHelloModule(void)
    {
        napi_module_register(&demoModule);
    }
    
  • 定义一个测试的类(TestNapi)

    class NapiTest{
    public:
        NapiTest(){}
        ~NapiTest(){}
        static napi_value SetMsg(napi_env env, napi_callback_info info) {
            napi_value result = nullptr;
            napi_get_undefined(env, &result);
            char _msg[128] = {0};
            napi_value msgvalue;
            size_t argc = 1, size = 0;
    
            if (napi_get_cb_info(env, info, &argc, &msgvalue, nullptr, nullptr) !=
                napi_ok) {
                return result;
            }
            
            if (napi_get_value_string_utf8(env, msgvalue, _msg, sizeof(_msg), &size) != 
                napi_ok) {
                return result;
            }
            
            return result;
        }
    
        static napi_value GetMsg(napi_env env, napi_callback_info info) {
            napi_value result;
            char *_msg = "hello NapiTest";
            if (napi_create_string_utf8(env, _msg, strlen(_msg), &result) != napi_ok) {
                napi_get_undefined(env, &result);
                return nullptr;
            }
            return result;
        }
        
        napi_value Create(napi_env env, void *data){
        }
    };
    
  • 定义一个全局的生命周期管理的变量

    static napi_ref g_Constructor = nullptr;
    
  • 将类的方法定义到一个napi_property_descriptor的数组 <br />
    特别申明:此步骤及以下步骤都在initi方法中完成。

    static napi_value Init(napi_env env, napi_value exports)
    {
      napi_property_descriptor desc[] = {
          { "SetMsg", nullptr, NapiTest::SetMsg, nullptr, nullptr, nullptr,
             napi_default, nullptr },
          { "GetMsg", nullptr, NapiTest::GetMsg, nullptr, nullptr, nullptr,
            napi_default,nullptr },
        };
    }
    
  • 将测试类定义到js类,并创建调用测试类的构造函数

    napi_value constructor = nullptr;
    if (napi_define_class(env, NAPI_CLASS_NAME, NAPI_AUTO_LENGTH, Constructor, nullptr, sizeof(desc) / sizeof(desc[0]),desc, &constructor) != napi_ok) {
        return nullptr;
    }
    

    其中Constructor构造函数如下:

    static napi_value Constructor(napi_env env, napi_callback_info info) {
        napi_value thisVar = nullptr;
        napi_get_undefined(env, &thisVar);
        napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    
        return thisVar;
    }
    

    如需要快速释放构建函数中创建的对象,也可以在构造函数中绑定一个类析构函数:

    static napi_value Constructor(napi_env env, napi_callback_info info) {
        napi_value thisVar = nullptr;
        napi_get_undefined(env, &thisVar);
        napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    
        std::unique_ptr<NapiTest> reference = std::make_unique<NapiTest>();
        status = napi_wrap(env, thisVar, reinterpret_cast<void *>(reference),
                            Destructor, nullptr, nullptr);
        
        return thisVar;
    }
    // 类析构函数,释放有Constructor构建时新建的对象
    static void Destructor(napi_env env, void *nativeObject, void *finalize)
    {
        NapiTest *test = reinterpret_cast<NapiTest*>(nativeObject);
        test->~NapiTest();
    }
    
  • 创建生命周期

    if (napi_create_reference(env, constructor, 1, &g_Constructor) != napi_ok) {
        return nullptr;
    }
    
  • 将生命周期变量作为导出对象的传入属性。

    if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) != napi_ok) {
        return nullptr;
    }
    
  • 设置导出对象的属性。

    if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) {
        return nullptr;
    }
    

到此,我们就完成了js类的定义以及相关生命周期管理的设置,该如何创建生命周期范围内的变量呢?我们可以在NapiTest类中定义一个方法,用于创建在该生命周期范围内的变量:

napi_value Create(napi_env env, void *data){
    napi_status status;
    napi_value constructor = nullptr, result = nullptr;
    // 获取生命周期变量
    status = napi_get_reference_value(env, g_Constructor, &constructor);
    if (status != napi_ok) {
        return nullptr;
    }
    /**
        do smoethings
    */
    // 创建生命周期内的对象并将其返回
    status = napi_new_instance(env, constructor, 0, nullptr, &result);
    if (status != napi_ok) {
        return nullptr;
    }

    return result;
}

使用到的函数:

napi_status napi_get_reference_value(napi_env env, napi_ref ref, uint32_t* result)

功能:获取当前引用的napi_value数据

参数说明:

  • [in] env: 当前环境变量
  • [in] ref: 引用计数的对象
  • [out] result: 引用计数的对象绑定的napi_value数据

返回:napi_status,成功返回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

推荐阅读更多精彩内容