• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Multithreaded Operations with Custom Native Sendable Objects
2
3ArkTS supports custom native Sendable objects, which provide efficient inter-thread communication through pass-by-reference. This is particularly valuable for scenarios requiring inter-thread communication of large custom objects, such as when a child thread retrieves database data and returns it to the host thread.
4
5The following demonstrates how to implement shared data access across concurrent instances using custom native Sendable objects.
6
71. Customize a Sendable class in the interface declaration.
8
9   ```ts
10   // Index.d.ts
11   @Sendable
12   export class MyObject {
13     constructor(arg: number);
14     plusOne(): number;
15
16     public get value();
17     public set value(newVal: number);
18   }
19   ```
20
212. Configure the build environment.
22
23    ```cmake
24    # CMakeLists.txt
25    # the minimum version of CMake.
26    cmake_minimum_required(VERSION 3.5.0)
27    project(napi_wrap_sendable_demo)
28
29    set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
30
31    if(DEFINED PACKAGE_FIND_FILE)
32        include(${PACKAGE_FIND_FILE})
33    endif()
34
35    include_directories(${NATIVERENDER_ROOT_PATH}
36                        ${NATIVERENDER_ROOT_PATH}/include)
37
38    add_library(entry SHARED napi_init.cpp)
39    target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)
40    ```
41
423. Implement the features in native code, such as obtaining values, setting values, and incrementing values.
43
44    ```cpp
45    // napi_init.cpp
46    #include "napi/native_api.h"
47    #include "hilog/log.h"
48
49    // A native class. Its instance is wrapped in an ArkTS Sendable object.
50    class MyObject {
51    public:
52        static napi_value Init(napi_env env, napi_value exports);
53        static void Destructor(napi_env env, void *nativeObject, void *finalize_hint);
54
55    private:
56        explicit MyObject(double value_ = 0);
57        ~MyObject();
58
59        static napi_value New(napi_env env, napi_callback_info info);
60        static napi_value GetValue(napi_env env, napi_callback_info info);
61        static napi_value SetValue(napi_env env, napi_callback_info info);
62        static napi_value PlusOne(napi_env env, napi_callback_info info);
63
64        double value_;
65        napi_env env_;
66    };
67
68    static thread_local napi_ref g_ref = nullptr;
69
70    MyObject::MyObject(double value) : value_(value), env_(nullptr) {}
71
72    MyObject::~MyObject() {}
73
74    void MyObject::Destructor(napi_env env, void *nativeObject, [[maybe_unused]] void *finalize_hint)
75    {
76        OH_LOG_INFO(LOG_APP, "MyObject::Destructor called");
77        reinterpret_cast<MyObject *>(nativeObject)->~MyObject();
78    }
79
80    // Bind the ArkTS Sendable object to a C++ object within a constructor.
81    napi_value MyObject::New(napi_env env, napi_callback_info info)
82    {
83        OH_LOG_INFO(LOG_APP, "MyObject::New called");
84
85        napi_value newTarget;
86        napi_get_new_target(env, info, &newTarget);
87        if (newTarget != nullptr) {
88            // Use the call mode new MyObject(...).
89            size_t argc = 1;
90            napi_value args[1];
91            napi_value jsThis;
92            napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr);
93
94            double value = 0.0;
95            napi_valuetype valuetype;
96            napi_typeof(env, args[0], &valuetype);
97            if (valuetype != napi_undefined) {
98                napi_get_value_double(env, args[0], &value);
99            }
100
101            MyObject *obj = new MyObject(value);
102
103            obj->env_ = env;
104            // Use napi_wrap_sendable to bind the ArkTS Sendable object jsThis to the C++ object obj.
105            napi_wrap_sendable(env, jsThis, reinterpret_cast<void *>(obj), MyObject::Destructor, nullptr);
106
107            return jsThis;
108        } else {
109            // Use the call mode MyObject(...).
110            size_t argc = 1;
111            napi_value args[1];
112            napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
113
114            napi_value cons;
115            napi_get_reference_value(env, g_ref, &cons);
116            napi_value instance;
117            napi_new_instance(env, cons, argc, args, &instance);
118
119            return instance;
120        }
121    }
122
123    // Obtain the value of the native object.
124    napi_value MyObject::GetValue(napi_env env, napi_callback_info info)
125    {
126        OH_LOG_INFO(LOG_APP, "MyObject::GetValue called");
127
128        napi_value jsThis;
129        napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);
130
131        MyObject *obj;
132        // Use napi_unwrap_sendable to retrieve obj (the C++ object) previously wrapped in jsThis (the ArkTS Sendable object), and perform subsequent operations.
133        napi_unwrap_sendable(env, jsThis, reinterpret_cast<void **>(&obj));
134        napi_value num;
135        napi_create_double(env, obj->value_, &num);
136
137        return num;
138    }
139
140    // Set the value of the native object.
141    napi_value MyObject::SetValue(napi_env env, napi_callback_info info)
142    {
143        OH_LOG_INFO(LOG_APP, "MyObject::SetValue called");
144
145        size_t argc = 1;
146        napi_value value;
147        napi_value jsThis;
148
149        napi_get_cb_info(env, info, &argc, &value, &jsThis, nullptr);
150
151        MyObject *obj;
152        // Use napi_unwrap_sendable to retrieve obj (the C++ object) previously wrapped in jsThis (the ArkTS Sendable object), and perform subsequent operations.
153        napi_unwrap_sendable(env, jsThis, reinterpret_cast<void **>(&obj));
154        napi_get_value_double(env, value, &obj->value_);
155
156        return nullptr;
157    }
158
159    // Increase the value of the native object by 1.
160    napi_value MyObject::PlusOne(napi_env env, napi_callback_info info)
161    {
162        OH_LOG_INFO(LOG_APP, "MyObject::PlusOne called");
163
164        napi_value jsThis;
165        napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);
166
167        MyObject *obj;
168        // Use napi_unwrap_sendable to retrieve obj (the C++ object) previously wrapped in jsThis (the ArkTS Sendable object), and perform subsequent operations.
169        napi_unwrap_sendable(env, jsThis, reinterpret_cast<void **>(&obj));
170        obj->value_ += 1;
171        napi_value num;
172        napi_create_double(env, obj->value_, &num);
173
174        return num;
175    }
176
177    napi_value MyObject::Init(napi_env env, napi_value exports)
178    {
179        napi_value num;
180        napi_create_double(env, 0, &num);
181        napi_property_descriptor properties[] = {
182            {"value", nullptr, nullptr, GetValue, SetValue, nullptr, napi_default, nullptr},
183            {"plusOne", nullptr, PlusOne, nullptr, nullptr, nullptr, napi_default, nullptr},
184        };
185
186        napi_value cons;
187        // Define a Sendable class MyObject.
188        napi_define_sendable_class(env, "MyObject", NAPI_AUTO_LENGTH, New, nullptr,
189                                sizeof(properties) / sizeof(properties[0]), properties, nullptr, &cons);
190
191        napi_create_reference(env, cons, 1, &g_ref);
192        // Mount the MyObject class to the exports object.
193        napi_set_named_property(env, exports, "MyObject", cons);
194        return exports;
195    }
196
197    EXTERN_C_START
198    // Initialize the module.
199    static napi_value Init(napi_env env, napi_value exports) {
200        MyObject::Init(env, exports);
201        return exports;
202    }
203    EXTERN_C_END
204
205    // Information about the module. Record information such as the Init() function and module name.
206    static napi_module nativeModule = {
207        .nm_version = 1,
208        .nm_flags = 0,
209        .nm_filename = nullptr,
210        .nm_register_func = Init,
211        .nm_modname = "entry",
212        .nm_priv = nullptr,
213        .reserved = {0},
214    };
215
216    // This function is automatically called when the.so file is loaded to register the nativeModule module with the system.
217    extern "C" __attribute__((constructor)) void RegisterObjectWrapModule() { napi_module_register(&nativeModule); }
218    ```
219
2204. On the ArkTS side, define a Sendable instance in the UI main thread and pass it to the TaskPool child thread. The child thread processes the data and returns the data to the UI main thread, which can continue to access the Sendable instance.
221
222   ```ts
223   // Index.ets
224   import { MyObject } from 'libentry.so';
225   import { taskpool } from '@kit.ArkTS';
226
227   @Concurrent
228   async function Sum(object: MyObject) {
229     object.value = 2000;
230     let num = object.plusOne();
231     console.info("taskpool thread num is " + num); // taskpool thread num is 2001
232     return num;
233   }
234
235   @Entry
236   @Component
237   struct Index {
238     @State message: string = 'Hello World';
239
240     build() {
241       Row() {
242         Column() {
243           Text(this.message)
244             .fontSize($r('app.float.page_text_font_size'))
245             .fontWeight(FontWeight.Bold)
246             .onClick( async () => {
247               let object : MyObject = new MyObject(0);
248               object.value = 1023;
249               let num = object.plusOne();
250               console.info("host thread num1 is " + num); // host thread num1 is 1024
251               let task = new taskpool.Task(Sum, object);
252               let result = await taskpool.execute(task);
253               console.info("host thread result is " + result); // host thread result is 2001
254               console.info("host thread num2 is " + object.value); // host thread num2 is 2001
255             })
256         }
257         .width('100%')
258       }
259       .height('100%')
260     }
261   }
262   ```
263