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

HarmonyOS的API提供进程内跨ArkTS线程共享的强引用能力。相较于napi\_ref，napi\_sendable\_ref支持跨ArkTS线程操作，但同时也存在一些限制。

#### 场景介绍

开发者可使用[napi\_create\_strong\_sendable\_reference](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_create_strong_sendable_reference)接口创建指向Sendable ArkTS对象的Sendable强引用，使用[napi\_get\_strong\_sendable\_reference\_value](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_get_strong_sendable_reference_value)获取被引用的ArkTS对象，使用[napi\_delete\_strong\_sendable\_reference](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_delete_strong_sendable_reference)删除Sendable强引用。这些操作既可以在同一ArkTS线程进行，也可在不同ArkTS线程进行。

#### Sendable强引用对象关联接口

| 接口 | 描述 |
| :-- | :-- |
| [napi\_create\_strong\_sendable\_reference](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_create_strong_sendable_reference) | 创建指向Sendable ArkTS对象的Sendable强引用。 |
| [napi\_delete\_strong\_sendable\_reference](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#napi_delete_strong_sendable_reference) | 删除Sendable强引用。 |
| [napi\_get\_strong\_sendable\_reference\_value](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/napi#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对象](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-sendable#sendable支持的数据类型)创建napi\_sendable\_ref。
2.  napi\_sendable\_ref可跨ArkTS线程使用，在多线程操作时，调用者需自己保证释放时机，防止出现释放后使用的问题。
3.  同一进程内，同时存活的napi\_sendable\_ref最大数量为51200个。
4.  napi\_sendable\_ref与其他引用的类型不同，因此不可将napi\_ref、napi\_strong\_ref等其他引用强转成napi\_sendable\_ref。napi\_delete\_strong\_sendable\_reference和napi\_get\_strong\_sendable\_reference\_value接口仅允许接收由napi\_create\_strong\_sendable\_reference创建的napi\_sendable\_ref。
5.  在使用napi\_create\_strong\_sendable\_reference、napi\_get\_strong\_sendable\_reference\_value和napi\_delete\_strong\_sendable\_reference接口时，调用者需要保证传入的env参数是当前调用接口的ArkTS线程环境对象，避免将其他ArkTS线程的env作为参数传入导致出现[多线程安全问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-ark-runtime-detection#section19357830121120)。