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

在ArkTS应用开发中，有很多场景需要将ArkTS对象与Native对象进行绑定。ArkTS对象将数据写入Native对象，Native对象再将数据写入目的地。例如，将ArkTS对象中的数据写入C++数据库场景。

Native Transferable对象有两种模式：共享模式和转移模式。本示例将详细说明如何实现这两种模式。

1.  Native实现各项功能。
    
    // napi\_init.cpp
    #include <mutex>
    #include <unordered\_set>
    #include "napi/native\_api.h"
    #include <hilog/log.h>
    
    class CustomNativeObject {
    public:
        CustomNativeObject() {}
        ~CustomNativeObject() = default;
        static CustomNativeObject& GetInstance()
        {
            static CustomNativeObject instance;
            return instance;
        }
    
        static napi\_value GetAddress(napi\_env env, napi\_callback\_info info)
        {
            napi\_value thisVar = nullptr;
            napi\_get\_cb\_info(env, info, nullptr, nullptr, &thisVar, nullptr);
            if (thisVar == nullptr) {
                return nullptr;
            }
            void\* object = nullptr;
            napi\_unwrap(env, thisVar, &object);
            if (object == nullptr) {
                return nullptr;
            }
            
            uint64\_t addressVal = reinterpret\_cast<uint64\_t>(object);
            napi\_value address = nullptr;
            napi\_create\_bigint\_uint64(env, addressVal, &address);
            return address;
        }
    
        // 获取数组大小
        static napi\_value GetSetSize(napi\_env env, napi\_callback\_info info)
        {
            napi\_value thisVar = nullptr;
            napi\_get\_cb\_info(env, info, nullptr, nullptr, &thisVar, nullptr);
            if (thisVar == nullptr) {
                return nullptr;
            }
            void\* object = nullptr;
            napi\_unwrap(env, thisVar, &object);
            if (object == nullptr) {
                return nullptr;
            }
            CustomNativeObject\* obj = static\_cast<CustomNativeObject\*>(object);
            std::lock\_guard<std::mutex> lock(obj->numberSetMutex\_);
            uint32\_t setSize = reinterpret\_cast<CustomNativeObject\*>(object)->numberSet\_.size();
            napi\_value napiSize = nullptr;
            napi\_create\_uint32(env, setSize, &napiSize);
            return napiSize;
        }
    
        // 往数组里插入元素
        static napi\_value Store(napi\_env env, napi\_callback\_info info)
        {
            size\_t argc = 1;
            napi\_value args\[1\] = {nullptr};
            napi\_value thisVar = nullptr;
            napi\_get\_cb\_info(env, info, &argc, args, &thisVar, nullptr);
            if (argc != 1) {
                napi\_throw\_error(env, nullptr, "Store args number must be one.");
                return nullptr;
            }
            napi\_valuetype type = napi\_undefined;
            napi\_typeof(env, args\[0\], &type);
            if (type != napi\_number) {
                napi\_throw\_error(env, nullptr, "Store args is not number.");
                return nullptr;
            }
            if (thisVar == nullptr) {
                return nullptr;
            }
            
            void\* object = nullptr;
            napi\_unwrap(env, thisVar, &object);
            if (object == nullptr) {
                return nullptr;
            }
            
            uint32\_t value = 0;
            napi\_get\_value\_uint32(env, args\[0\], &value);
            CustomNativeObject\* obj = static\_cast<CustomNativeObject\*>(object);
            std::lock\_guard<std::mutex> lock(obj->numberSetMutex\_);
            reinterpret\_cast<CustomNativeObject \*>(object)->numberSet\_.insert(value);
            return nullptr;
        }
    
        // 删除数组元素
        static napi\_value Erase(napi\_env env, napi\_callback\_info info)
        {
            size\_t argc = 1;
            napi\_value args\[1\] = {nullptr};
            napi\_value thisVar = nullptr;
            napi\_get\_cb\_info(env, info, &argc, args, &thisVar, nullptr);
            if (argc != 1) {
                napi\_throw\_error(env, nullptr, "Erase args number must be one.");
                return nullptr;
            }
            napi\_valuetype type = napi\_undefined;
            napi\_typeof(env, args\[0\], &type);
            if (type != napi\_number) {
                napi\_throw\_error(env, nullptr, "Erase args is not number.");
                return nullptr;
            }
            if (thisVar == nullptr) {
                return nullptr;
            }
            
            void\* object = nullptr;
            napi\_unwrap(env, thisVar, &object);
            if (object == nullptr) {
                return nullptr;
            }
            
            uint32\_t value = 0;
            napi\_get\_value\_uint32(env, args\[0\], &value);
            
            CustomNativeObject\* obj = static\_cast<CustomNativeObject\*>(object);
            std::lock\_guard<std::mutex> lock(obj->numberSetMutex\_);
            reinterpret\_cast<CustomNativeObject \*>(object)->numberSet\_.erase(value);
            return nullptr;
        }
    
        // 清空数组
        static napi\_value Clear(napi\_env env, napi\_callback\_info info)
        {
            napi\_value thisVar = nullptr;
            napi\_get\_cb\_info(env, info, nullptr, nullptr, &thisVar, nullptr);
            if (thisVar == nullptr) {
                return nullptr;
            }
            void\* object = nullptr;
            napi\_unwrap(env, thisVar, &object);
            if (object == nullptr) {
                return nullptr;
            }
            CustomNativeObject\* obj = static\_cast<CustomNativeObject\*>(object);
            std::lock\_guard<std::mutex> lock(obj->numberSetMutex\_);
            reinterpret\_cast<CustomNativeObject \*>(object)->numberSet\_.clear();
            return nullptr;
        }
        
        // 设置传输模式
        static napi\_value SetTransferDetached(napi\_env env, napi\_callback\_info info)
        {
            size\_t argc = 1;
            napi\_value args\[1\];
            napi\_value thisVar;
            napi\_get\_cb\_info(env, info, &argc, args, &thisVar, nullptr);
            if (argc != 1) {
                napi\_throw\_error(env, nullptr, "SetTransferDetached args number must be one.");
                return nullptr;
            }
            
            if (thisVar == nullptr) {
                return nullptr;
            }
            
            napi\_valuetype type = napi\_undefined;
            napi\_typeof(env, args\[0\], &type);
            if (type != napi\_boolean) {
                napi\_throw\_error(env, nullptr, "SetTransferDetached args is not boolean.");
                return nullptr;
            }
            
            bool isDetached;
            napi\_get\_value\_bool(env, args\[0\], &isDetached);
            
            void\* object = nullptr;
            napi\_unwrap(env, thisVar, &object);
            if (object == nullptr) {
                return nullptr;
            }
            CustomNativeObject\* obj = static\_cast<CustomNativeObject\*>(object);
            std::lock\_guard<std::mutex> lock(obj->numberSetMutex\_);
            obj->isDetached\_ = isDetached;
            return nullptr;
        }
        
        bool isDetached\_ = false;
    
    private:
        CustomNativeObject(const CustomNativeObject &) = delete;
        CustomNativeObject &operator=(const CustomNativeObject &) = delete;
    
        std::unordered\_set<uint32\_t> numberSet\_{};
        std::mutex numberSetMutex\_{};
    };
    
    void FinalizeCallback(napi\_env env, void \*data, void \*hint)
    {
        return;
    }
    
    // 解绑回调，在序列化时调用，可在对象解绑时执行一些清理操作
    void\* DetachCallback(napi\_env env, void \*value, void \*hint)
    {
        if (hint == nullptr) {
            return value;
        }
        napi\_value jsObject = nullptr;
        napi\_get\_reference\_value(env, reinterpret\_cast<napi\_ref>(hint), &jsObject);
        void\* object = nullptr;
        if (static\_cast<CustomNativeObject\*>(value)->isDetached\_) {
            napi\_remove\_wrap(env, jsObject, &object);
        }
        return value;
    }
    
    // 绑定回调，在反序列化时调用
    napi\_value AttachCallback(napi\_env env, void\* value, void\* hint)
    {
        napi\_value object = nullptr;
        napi\_create\_object(env, &object);
        napi\_property\_descriptor desc\[\] = {
            {"getAddress", nullptr, CustomNativeObject::GetAddress, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"getSetSize", nullptr, CustomNativeObject::GetSetSize, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"store", nullptr, CustomNativeObject::Store, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"erase", nullptr, CustomNativeObject::Erase, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"clear", nullptr, CustomNativeObject::Clear, nullptr, nullptr, nullptr, napi\_default, nullptr}};
        napi\_define\_properties(env, object, sizeof(desc) / sizeof(desc\[0\]), desc);
        // 将JS对象object和native对象value生命周期进行绑定
        napi\_wrap(env, object, value, FinalizeCallback, nullptr, nullptr);
        // JS对象携带native信息
        napi\_coerce\_to\_native\_binding\_object(env, object, DetachCallback, AttachCallback, value, nullptr);
        return object;
    }
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        napi\_property\_descriptor desc\[\] = {
            {"getAddress", nullptr, CustomNativeObject::GetAddress, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"getSetSize", nullptr, CustomNativeObject::GetSetSize, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"store", nullptr, CustomNativeObject::Store, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"erase", nullptr, CustomNativeObject::Erase, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"clear", nullptr, CustomNativeObject::Clear, nullptr, nullptr, nullptr, napi\_default, nullptr},
            {"setTransferDetached", nullptr, CustomNativeObject::SetTransferDetached,
                nullptr, nullptr, nullptr, napi\_default, nullptr}};
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        auto &object = CustomNativeObject::GetInstance();
        napi\_wrap(env, exports, reinterpret\_cast<void\*>(&object), FinalizeCallback, nullptr, nullptr);
        napi\_ref exportsRef;
        napi\_create\_reference(env, exports, 1, &exportsRef);
        napi\_coerce\_to\_native\_binding\_object(env, exports, DetachCallback,
            AttachCallback, reinterpret\_cast<void\*>(&object), exportsRef);
        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);
    }
    
2.  在ArkTS中声明接口。
    
    // Index.d.ts
    export const getAddress: () => number;
    export const getSetSize: () => number;
    export const store: (a: number) => void;
    export const erase: (a: number) => void;
    export const clear: () => void;
    export const setTransferDetached: (b : boolean) => number;
    
3.  ArkTS对象调用Native侧实现的各项功能。
    
    在转移模式下，跨线程传递后，原来的ArkTS对象与Native对象解绑，因此不能继续访问。示例如下：
    
    import testNapi from 'libentry.so';
    import { taskpool } from '@kit.ArkTS';
    
    @Concurrent
    function getAddress() {
      let address: number = testNapi.getAddress();
      console.info('taskpool:: address is ' + address);
    }
    
    @Concurrent
    function store(a:number, b:number, c:number) {
      let size:number = testNapi.getSetSize();
      console.info('set size is ' + size + ' before store');
      testNapi.store(a);
      testNapi.store(b);
      testNapi.store(c);
      size = testNapi.getSetSize();
      console.info('set size is ' + size + ' after store');
    }
    
    @Concurrent
    function erase(a:number) {
      let size:number = testNapi.getSetSize();
      console.info('set size is ' + size + ' before erase');
      testNapi.erase(a);
      size = testNapi.getSetSize();
      console.info('set size is ' + size + ' after erase');
    }
    
    @Concurrent
    function clear() {
      let size:number = testNapi.getSetSize();
      console.info('set size is ' + size + ' before clear');
      testNapi.clear();
      size = testNapi.getSetSize();
      console.info('set size is ' + size + ' after clear');
    }
    
    // 转移模式
    async function test(): Promise<void> {
      // setTransferDetached 设置为true，表示传输方式为转移模式
      testNapi.setTransferDetached(true);
      let address:number = testNapi.getAddress();
      console.info('host thread address is ' + address);
    
      let task1 = new taskpool.Task(getAddress, testNapi);
      await taskpool.execute(task1);
    
      let task2 = new taskpool.Task(store, 1, 2, 3);
      await taskpool.execute(task2);
    
      let task3 = new taskpool.Task(store, 4, 5, 6);
      await taskpool.execute(task3);
    
      // 由于已经设置了转移模式，且testNapi已跨线程传递，所以主线程无法继续访问到Native对象的值
      let size:number = testNapi.getSetSize();
      // 输出的日志为“host thread size is undefined”
      console.info('host thread size is ' + size);
    
      let task4 = new taskpool.Task(erase, 3);
      await taskpool.execute(task4);
    
      let task5 = new taskpool.Task(erase, 5);
      await taskpool.execute(task5);
    
      let task6 = new taskpool.Task(clear);
      await taskpool.execute(task6);
    }
    
    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World';
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() => {
                test();
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
    在共享模式下，跨线程传递后，原来的ArkTS对象还可以继续访问Native对象。示例如下：
    
    // Index.ets
    import testNapi from 'libentry.so';
    import { taskpool } from '@kit.ArkTS';
    
    @Concurrent
    function getAddress() {
      let address: number = testNapi.getAddress();
      console.info('taskpool:: address is ' + address);
    }
    
    @Concurrent
    function store(a:number, b:number, c:number) {
      let size:number = testNapi.getSetSize();
      console.info('set size is ' + size + ' before store');
      testNapi.store(a);
      testNapi.store(b);
      testNapi.store(c);
      size = testNapi.getSetSize();
      console.info('set size is ' + size + ' after store');
    }
    
    @Concurrent
    function erase(a:number) {
      let size:number = testNapi.getSetSize();
      console.info('set size is ' + size + ' before erase');
      testNapi.erase(a);
      size = testNapi.getSetSize();
      console.info('set size is ' + size + ' after erase');
    }
    
    @Concurrent
    function clear() {
      let size:number = testNapi.getSetSize();
      console.info('set size is ' + size + ' before clear');
      testNapi.clear();
      size = testNapi.getSetSize();
      console.info('set size is ' + size + ' after clear');
    }
    
    // 共享模式
    async function test(): Promise<void> {
      let address:number = testNapi.getAddress();
      console.info('host thread address is ' + address);
    
      let task1 = new taskpool.Task(getAddress, testNapi);
      await taskpool.execute(task1);
    
      let task2 = new taskpool.Task(store, 1, 2, 3);
      await taskpool.execute(task2);
    
      let task3 = new taskpool.Task(store, 4, 5, 6);
      await taskpool.execute(task3);
    
      // 由于默认的传输模式为共享模式，testNapi跨线程传递后，主线程可以继续访问Native对象的值
      let size:number = testNapi.getSetSize();
      // 输出的日志为“host thread size is 6”
      console.info('host thread size is ' + size);
    
      let task4 = new taskpool.Task(erase, 3);
      await taskpool.execute(task4);
    
      let task5 = new taskpool.Task(erase, 5);
      await taskpool.execute(task5);
    
      let task6 = new taskpool.Task(clear);
      await taskpool.execute(task6);
    }
    
    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World';
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() => {
                test();
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }