使用扩展的Node-API接口创建对ArkTS对象的Sendable强引用

OpenHarmony的API提供进程内跨ArkTS线程共享的强引用能力。相较于napi_refnapi_sendable_ref支持跨ArkTS线程操作,但同时也存在一些限制。

场景介绍

开发者可使用napi_create_strong_sendable_reference接口创建指向Sendable ArkTS对象的Sendable强引用,使用napi_get_strong_sendable_reference_value获取被引用的ArkTS对象,使用napi_delete_strong_sendable_reference删除Sendable强引用。这些操作即可在同一ArkTS线程进行,也可在不同ArkTS线程进行。

Sendable强引用对象关联接口

接口 描述
napi_create_strong_sendable_reference 创建指向Sendable ArkTS对象的Sendable强引用。
napi_delete_strong_sendable_reference 删除Sendable强引用。
napi_get_strong_sendable_reference_value 根据Sendable强引用获取其关联的ArkTS对象值。

示例代码

  • 模块注册

    // napi_init.cpp
    #include "napi/native_api.h"
    #include <cstdlib>
    #include <thread>
    
    #define ASSERT_EQ(a, b) \
    do {                    \
       if (a != b) {        \
          std::abort();     \
       }                    \
    } while(0)
    
    #define ASSERT_CHECK_CALL(a) ASSERT_EQ(a, napi_ok)
    
    napi_sendable_ref sRef = nullptr;
    static napi_value CreateSendableRef(napi_env env, napi_callback_info info)
    {
       size_t argc = 1;
       napi_value args[1] = {nullptr};
    
       ASSERT_CHECK_CALL(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
       ASSERT_CHECK_CALL(napi_create_strong_sendable_reference(env, args[0], &sRef));
       
       napi_value str = nullptr;
       ASSERT_CHECK_CALL(napi_create_string_utf8(env, "success", NAPI_AUTO_LENGTH, &str));
       return str;
    }
    
    static napi_value GetAndModifySendableRefValueInArkRuntime(napi_env env, napi_callback_info info)
    {
       // 此处省略调用者在主线程的业务逻辑
       // ...
       
       // 此处模拟调用者在其他ArkTS线程上获取napi_sendable_ref内的共享对象操作
       std::thread t1([]() {
          napi_env newEnv = nullptr;
          ASSERT_CHECK_CALL(napi_create_ark_runtime(&newEnv));
          napi_handle_scope scope = nullptr;
          ASSERT_CHECK_CALL(napi_open_handle_scope(newEnv, &scope));
          if (!sRef) {
             std::abort();
          }
          napi_value sObj = nullptr;
          ASSERT_CHECK_CALL(napi_get_strong_sendable_reference_value(newEnv, sRef, &sObj));
          
          // 校验sObj内容
          napi_value numValue = nullptr;
          ASSERT_CHECK_CALL(napi_get_named_property(newEnv, sObj, "num", &numValue));
          int32_t num = 0;
          ASSERT_CHECK_CALL(napi_get_value_int32(newEnv, numValue, &num));
          ASSERT_EQ(num, 1111);
          
          // 修改sObj内容
          napi_value newNum = nullptr;
          ASSERT_CHECK_CALL(napi_create_int32(newEnv, num * 2, &newNum));
          ASSERT_CHECK_CALL(napi_set_named_property(newEnv, sObj, "num", newNum));
          ASSERT_CHECK_CALL(napi_close_handle_scope(newEnv, scope));
          ASSERT_CHECK_CALL(napi_destroy_ark_runtime(&newEnv));
       });
       t1.join();
    
       napi_value str = nullptr;
       ASSERT_CHECK_CALL(napi_create_string_utf8(env, "success", NAPI_AUTO_LENGTH, &str));
       return str;
    }
    
    static napi_value GetSendableRefValueInWorkerOrTaskpool(napi_env env, napi_callback_info info)
    {
       // 此处省略调用者在worker/taskpool线程的业务逻辑
       // ...
    
       // 此处模拟调用者在其他Worker/Taskpool线程上获取napi_sendable_ref内的共享对象操作
       if (!sRef) {
          napi_value undefined = nullptr;
          napi_get_undefined(env, &undefined);
          return undefined;
       }
       napi_value sObj = nullptr;
       ASSERT_CHECK_CALL(napi_get_strong_sendable_reference_value(env, sRef, &sObj));
    
       // 校验sObj内容
       napi_value numValue = nullptr;
       ASSERT_CHECK_CALL(napi_get_named_property(env, sObj, "num", &numValue));
       int32_t num = 0;
       ASSERT_CHECK_CALL(napi_get_value_int32(env, numValue, &num));
       ASSERT_EQ(num, 1111);
    
       return sObj;
    }
    
    static napi_value CheckAndDeleteSendableRef(napi_env env, napi_callback_info info)
    {
       if (!sRef) {
          napi_value undefined = nullptr;
          ASSERT_CHECK_CALL(napi_get_undefined(env, &undefined));
          return undefined;
       }
    
       // 校验和删除ref的动作也可放在ArkTS线程中,此处示例为主线程
       // 校验sObj内容
       napi_value sObj = nullptr;
       ASSERT_CHECK_CALL(napi_get_strong_sendable_reference_value(env, sRef, &sObj));
       napi_value numValue = nullptr;
       ASSERT_CHECK_CALL(napi_get_named_property(env, sObj, "num", &numValue));
       int32_t num = 0;
       ASSERT_CHECK_CALL(napi_get_value_int32(env, numValue, &num));
       ASSERT_EQ(num, 2222);
    
       ASSERT_CHECK_CALL(napi_delete_strong_sendable_reference(env, sRef));
       sRef = nullptr;
    
       // 删除SendableRef
       napi_value str = nullptr;
       ASSERT_CHECK_CALL(napi_create_string_utf8(env, "success", NAPI_AUTO_LENGTH, &str));
       return str;
    }
    
    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports)
    {
       napi_property_descriptor desc[] = {
          { "createSendableRef", nullptr, CreateSendableRef, nullptr, nullptr, nullptr, napi_default, nullptr },
          { "getAndModifySendableRefValueInArkRuntime", nullptr, GetAndModifySendableRefValueInArkRuntime,
                nullptr,nullptr, nullptr, napi_default, nullptr },
          { "getSendableRefValueInWorkerOrTaskpool", nullptr, GetSendableRefValueInWorkerOrTaskpool,
                nullptr,nullptr, nullptr, napi_default, nullptr },
          { "checkAndDeleteSendableRef", nullptr, CheckAndDeleteSendableRef,
                nullptr, nullptr, nullptr, napi_default, nullptr },
       };
       napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), 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 = "entry",
       .nm_priv = ((void*)0),
       .reserved = { 0 },
    };
    
    extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
    {
       napi_module_register(&demoModule);
    }
    
    
  • 接口声明

    // index.d.ts
    export const createSendableRef: (a: object) => string;
    export const getAndModifySendableRefValueInArkRuntime: () => string;
    export const getSendableRefValueInWorkerOrTaskpool: () => object;
    export const checkAndDeleteSendableRef: () => string;
    
  • ArkTS代码示例

    // index.ets
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import testNapi from 'libentry.so';
    import { MessageEvents, taskpool, worker} from '@kit.ArkTS'
    
    const DOMAIN = 0x0000;
    
    @Sendable
    class SendableClass {
       num: number = 1111;
    }
    
    @Concurrent
    function TaskpoolFunc(data: string) {
       console.info('testTag, ' + data);
       let sObj = testNapi.getSendableRefValueInWorkerOrTaskpool() as SendableClass;
       sObj.num = 2222;
    }
    
    async function concurrentFunc() {
       let sObj = new SendableClass();
       hilog.info(DOMAIN, 'testTag', 'Test CreateSendableRef result = %{public}s',
          testNapi.createSendableRef(sObj));
       const task: taskpool.Task = new taskpool.Task(TaskpoolFunc,
          'Please check sendable ref value in taskpool thread');
       await taskpool.execute(task);
       let ret: string = testNapi.checkAndDeleteSendableRef();
       return ret;
    }
    
    @Entry
    @Component
    struct Index {
       @State TestMsg1: string = 'TestInArkRuntime';
       @State TestMsg2: string = 'TestInWorker';
       @State TestMsg3: string = 'TestInTaskpool';
    
       build() {
          Row() {
             Column() {
                Button(this.TestMsg1)
                   .fontSize($r('app.float.page_text_font_size'))
                   .fontWeight(FontWeight.Bold)
                   .onClick(() => {
                      let sObj = new SendableClass();
                      hilog.info(DOMAIN, 'testTag', 'Test CreateSendableRef result = %{public}s',
                         testNapi.createSendableRef(sObj));
                      hilog.info(DOMAIN, 'testTag', 'Test GetAndModifySendableRefValue result = %{public}s',
                         testNapi.getAndModifySendableRefValueInArkRuntime());
                      this.TestMsg1 = testNapi.checkAndDeleteSendableRef();
                   })
                Button(this.TestMsg2)
                   .fontSize($r('app.float.page_text_font_size'))
                   .fontWeight(FontWeight.Bold)
                   .onClick(() => {
                      let sObj = new SendableClass();
                      hilog.info(DOMAIN, 'testTag', 'Test CreateSendableRef result = %{public}s',
                      testNapi.createSendableRef(sObj));
                      const worker1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/Worker.ets');
                      worker1.onmessage = (e: MessageEvents) => {
                         let data: string = e.data;
                         hilog.info(DOMAIN, 'testTag', data);
                         this.TestMsg2 = testNapi.checkAndDeleteSendableRef();
                      }
                      worker1.postMessage('Please check sendable ref value in worker thread');
                   })
                Button(this.TestMsg3)
                   .fontSize($r('app.float.page_text_font_size'))
                   .fontWeight(FontWeight.Bold)
                   .onClick(() => {
                      concurrentFunc().then((ret) => {
                         this.TestMsg3 = ret;
                      });
                   })
             }
             .width('100%')
          }
          .height('100%')
       }
    }
    
    // Worker.ets
    import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
    import testNapi from 'libentry.so';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    const DOMAIN = 0x0000;
    
    @Sendable
    class SendableClass {
       num: number = 1111;
    }
    
    const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
    
    /**
    * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
    * The event handler is executed in the worker thread.
    *
    * @param event message data
    */
    workerPort.onmessage = (event: MessageEvents) => {
       let data: string = event.data;
       hilog.info(DOMAIN, 'testTag', data);
       let sObj = testNapi.getSendableRefValueInWorkerOrTaskpool() as SendableClass;
       sObj.num = 2222;
       workerPort.postMessage('Please check sendable ref value and delete ref');
    };
    
    /**
    * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
    * The event handler is executed in the worker thread.
    *
    * @param event message data
    */
    workerPort.onmessageerror = (event: MessageEvents) => {
    };
    
    /**
    * Defines the event handler to be called when an exception occurs during worker execution.
    * The event handler is executed in the worker thread.
    *
    * @param event error message
    */
    workerPort.onerror = (event: ErrorEvent) => {
    };
    

注意事项

  1. 只能为Sendable对象创建napi_sendable_ref
  2. napi_sendable_ref可跨ArkTS线程使用,在多线程操作时,调用者需自己保证释放时机,防止出现释放后使用的问题。
  3. 同一进程内,同时存活的napi_sendable_ref最大数量为51200个。
  4. 不可将napi_refnapi_strong_ref等其他引用强转成napi_sendable_refnapi_delete_strong_sendable_referencenapi_get_strong_sendable_reference_value接口仅允许接收由napi_create_strong_sendable_reference创建的napi_sendable_ref