自定义Native Sendable对象的多线程操作场景
============================

ArkTS支持开发者自定义Native Sendable对象，Sendable对象提供了并发实例间高效的通信能力，即引用传递，适用于开发者自定义大对象需要线程间通信的场景，例如子线程读取数据库数据并返回给宿主线程。

本示例将详细说明如何使用自定义Native Sendable对象实现并发实例间数据共享。

1.  接口声明中自定义Sendable类。
    
    // Index.d.ets
    @Sendable
    export class MyObject {
      constructor(arg: number);
      plusOne(): number;
    
      public get value(): number;
      public set value(newVal: number);
    }
    
2.  编译配置。
    
    \# CMakeLists.txt
    # the minimum version of CMake.
    cmake\_minimum\_required(VERSION 3.5.0)
    project(napi\_wrap\_sendable\_demo)
    
    set(NATIVERENDER\_ROOT\_PATH ${CMAKE\_CURRENT\_SOURCE\_DIR})
    
    if(DEFINED PACKAGE\_FIND\_FILE)
        include(${PACKAGE\_FIND\_FILE})
    endif()
    
    include\_directories(${NATIVERENDER\_ROOT\_PATH}
                        ${NATIVERENDER\_ROOT\_PATH}/include)
    
    add\_definitions("-DLOG\_DOMAIN=0x0000")
    add\_definitions("-DLOG\_TAG=\\"testTag\\"")
    
    add\_library(entry SHARED napi\_init.cpp)
    target\_link\_libraries(entry PUBLIC libace\_napi.z.so libhilog\_ndk.z.so)
    
3.  Native实现各项接口功能，例如取值、设置值或者给Native对象的值加1等功能。
    
    // napi\_init.cpp
    #include "napi/native\_api.h"
    #include "hilog/log.h"
    
    // 一个native类，它的实例在下面会包装在ArkTS的Sendable对象中
    class MyObject {
    public:
        static napi\_value Init(napi\_env env, napi\_value exports);
        static void Destructor(napi\_env env, void \*nativeObject, void \*finalizeHint);
    
    private:
        explicit MyObject(double value = 0);
        ~MyObject();
    
        static napi\_value New(napi\_env env, napi\_callback\_info info);
        static napi\_value GetValue(napi\_env env, napi\_callback\_info info);
        static napi\_value SetValue(napi\_env env, napi\_callback\_info info);
        static napi\_value PlusOne(napi\_env env, napi\_callback\_info info);
    
        double value\_;
        napi\_env env\_;
    };
    
    static thread\_local napi\_ref g\_ref = nullptr;
    
    MyObject::MyObject(double value) : value\_(value), env\_(nullptr) {}
    
    MyObject::~MyObject() {}
    
    void MyObject::Destructor(napi\_env env, void \*nativeObject, \[\[maybe\_unused\]\] void \*finalizeHint)
    {
        OH\_LOG\_INFO(LOG\_APP, "MyObject::Destructor called");
        reinterpret\_cast<MyObject \*>(nativeObject)->~MyObject();
    }
    
    // 在构造函数中绑定ArkTS Sendable对象与C++对象
    napi\_value MyObject::New(napi\_env env, napi\_callback\_info info)
    {
        OH\_LOG\_INFO(LOG\_APP, "MyObject::New called");
    
        napi\_value newTarget;
        napi\_get\_new\_target(env, info, &newTarget);
        if (newTarget != nullptr) {
            // 使用\`new MyObject(...)\`调用方式
            size\_t argc = 1;
            napi\_value args\[1\];
            napi\_value jsThis;
            napi\_get\_cb\_info(env, info, &argc, args, &jsThis, nullptr);
    
            double value = 0.0;
            napi\_valuetype valuetype;
            napi\_typeof(env, args\[0\], &valuetype);
            if (valuetype != napi\_undefined) {
                napi\_get\_value\_double(env, args\[0\], &value);
            }
    
            MyObject \*obj = new MyObject(value);
    
            obj->env\_ = env;
            // 通过napi\_wrap\_sendable将ArkTS Sendable对象jsThis与C++对象obj绑定
            napi\_wrap\_sendable(env, jsThis, reinterpret\_cast<void \*>(obj), MyObject::Destructor, nullptr);
    
            return jsThis;
        } else {
            // 使用\`MyObject(...)\`调用方式
            size\_t argc = 1;
            napi\_value args\[1\];
            napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
    
            napi\_value cons;
            napi\_get\_reference\_value(env, g\_ref, &cons);
            napi\_value instance;
            napi\_new\_instance(env, cons, argc, args, &instance);
    
            return instance;
        }
    }
    
    // 取出Native对象的值
    napi\_value MyObject::GetValue(napi\_env env, napi\_callback\_info info)
    {
        OH\_LOG\_INFO(LOG\_APP, "MyObject::GetValue called");
    
        napi\_value jsThis;
        napi\_get\_cb\_info(env, info, nullptr, nullptr, &jsThis, nullptr);
    
        MyObject \*obj;
        // 通过napi\_unwrap\_sendable将jsThis之前绑定的C++对象取出，并对其进行操作
        napi\_unwrap\_sendable(env, jsThis, reinterpret\_cast<void \*\*>(&obj));
        napi\_value num;
        napi\_create\_double(env, obj->value\_, &num);
    
        return num;
    }
    
    // 设置Native对象的值
    napi\_value MyObject::SetValue(napi\_env env, napi\_callback\_info info)
    {
        OH\_LOG\_INFO(LOG\_APP, "MyObject::SetValue called");
    
        size\_t argc = 1;
        napi\_value value;
        napi\_value jsThis;
    
        napi\_get\_cb\_info(env, info, &argc, &value, &jsThis, nullptr);
    
        MyObject \*obj;
        // 通过napi\_unwrap\_sendable将jsThis之前绑定的C++对象取出，并对其进行操作
        napi\_unwrap\_sendable(env, jsThis, reinterpret\_cast<void \*\*>(&obj));
        napi\_get\_value\_double(env, value, &obj->value\_);
    
        return nullptr;
    }
    
    // 给Native对象的值加1
    napi\_value MyObject::PlusOne(napi\_env env, napi\_callback\_info info)
    {
        OH\_LOG\_INFO(LOG\_APP, "MyObject::PlusOne called");
    
        napi\_value jsThis;
        napi\_get\_cb\_info(env, info, nullptr, nullptr, &jsThis, nullptr);
    
        MyObject \*obj;
        // 通过napi\_unwrap\_sendable将jsThis之前绑定的C++对象取出，并对其进行操作
        napi\_unwrap\_sendable(env, jsThis, reinterpret\_cast<void \*\*>(&obj));
        obj->value\_ += 1;
        napi\_value num;
        napi\_create\_double(env, obj->value\_, &num);
    
        return num;
    }
    
    napi\_value MyObject::Init(napi\_env env, napi\_value exports)
    {
        napi\_value num;
        napi\_create\_double(env, 0, &num);
        napi\_property\_descriptor properties\[\] = {
            {"value", nullptr, nullptr, GetValue, SetValue, nullptr, napi\_default, nullptr},
            {"plusOne", nullptr, PlusOne, nullptr, nullptr, nullptr, napi\_default, nullptr},
        };
    
        napi\_value cons;
        // 定义一个Sendable class MyObject
        napi\_define\_sendable\_class(env, "MyObject", NAPI\_AUTO\_LENGTH, New, nullptr,
                                   sizeof(properties) / sizeof(properties\[0\]), properties, nullptr, &cons);
    
        napi\_create\_reference(env, cons, 1, &g\_ref);
        // 在exports对象上挂载MyObject类
        napi\_set\_named\_property(env, exports, "MyObject", cons);
        return exports;
    }
    
    EXTERN\_C\_START
    // 模块初始化
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        MyObject::Init(env, exports);
        return exports;
    }
    EXTERN\_C\_END
    
    // 准备模块加载相关信息，将上述Init函数与本模块名等信息记录下来
    static napi\_module nativeModule = {
        .nm\_version = 1,
        .nm\_flags = 0,
        .nm\_filename = nullptr,
        .nm\_register\_func = Init,
        .nm\_modname = "entry",
        .nm\_priv = nullptr,
        .reserved = {0},
    };
    
    // 加载so时，自动调用该函数，将上述nativeModule模块注册到系统中
    extern "C" \_\_attribute\_\_((constructor)) void RegisterObjectWrapModule() { napi\_module\_register(&nativeModule); }
    
4.  ArkTS侧在UI主线程中定义Sendable实例对象并传递给TaskPool子线程，子线程处理完数据后返回UI主线程，UI主线程可以继续访问该Sendable实例对象。
    
    // Index.ets
    import { MyObject } from 'libentry.so';
    import { taskpool } from '@kit.ArkTS';
    
    @Concurrent
    async function sum(object: MyObject) {
      object.value = 2000;
      let num = object.plusOne();
      console.info('taskpool thread num is ' + num); // 日志输出：taskpool thread num is 2001
      return num;
    }
    
    @Entry
    @Component
    struct Index {
      @State message: string = '使用Sendable对象进行线程间通信';
    
      build() {
        Row() {
          Column() {
            Button(this.message)
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick( async () => {
                let object : MyObject = new MyObject(0);
                object.value = 1023;
                let num = object.plusOne();
                console.info('host thread num1 is ' + num); // 日志输出：host thread num1 is 1024
                let task = new taskpool.Task(sum, object);
                let result = await taskpool.execute(task);
                console.info('host thread result is ' + result); // 日志输出：host thread result is 2001
                console.info('host thread num2 is ' + object.value); // 日志输出：host thread num2 is 2001
                this.message = 'host thread num2 is ' + object.value;
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
5.  修改与Index.d.ets同目录下的配置文件oh-package.json5，配置如下：
    
    {
      "name": "libentry.so",
      "types": "./Index.d.ets",
      "version": "1.0.0",
      "description": "Please describe the basic information."
    }