• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Multithreaded Operations with Custom Native Transferable Objects
2
3In ArkTS application development, there are numerous scenarios that require binding ArkTS objects with native objects. ArkTS objects write data into native objects, which then transfer the data to the destination. A common example is writing data from an ArkTS object into a C++ database.
4
5Native Transferable objects support two distinct modes: shared mode and transfer mode. The following demonstrates how to implement both modes.
6
71. Implement features in native code.
8
9   ```cpp
10   // napi_init.cpp
11   #include <mutex>
12   #include <unordered_set>
13   #include "napi/native_api.h"
14   #include <hilog/log.h>
15
16   class CustomNativeObject {
17   public:
18       CustomNativeObject() {}
19       ~CustomNativeObject() = default;
20       static CustomNativeObject& GetInstance()
21       {
22           static CustomNativeObject instance;
23           return instance;
24       }
25
26       static napi_value GetAddress(napi_env env, napi_callback_info info)
27       {
28           napi_value thisVar = nullptr;
29           napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
30           if (thisVar == nullptr) {
31               return nullptr;
32           }
33           void* object = nullptr;
34           napi_unwrap(env, thisVar, &object);
35           if (object == nullptr) {
36               return nullptr;
37           }
38
39           uint64_t addressVal = reinterpret_cast<uint64_t>(object);
40           napi_value address = nullptr;
41           napi_create_bigint_uint64(env, addressVal, &address);
42           return address;
43       }
44
45       // Obtain the array size.
46       static napi_value GetSetSize(napi_env env, napi_callback_info info)
47       {
48           napi_value thisVar = nullptr;
49           napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
50           if (thisVar == nullptr) {
51               return nullptr;
52           }
53           void* object = nullptr;
54           napi_unwrap(env, thisVar, &object);
55           if (object == nullptr) {
56               return nullptr;
57           }
58           CustomNativeObject* obj = static_cast<CustomNativeObject*>(object);
59           std::lock_guard<std::mutex> lock(obj->numberSetMutex_);
60           uint32_t setSize = reinterpret_cast<CustomNativeObject*>(object)->numberSet_.size();
61           napi_value napiSize = nullptr;
62           napi_create_uint32(env, setSize, &napiSize);
63           return napiSize;
64       }
65
66       // Insert an element into the array.
67       static napi_value Store(napi_env env, napi_callback_info info)
68       {
69           size_t argc = 1;
70           napi_value args[1] = {nullptr};
71           napi_value thisVar = nullptr;
72           napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr);
73           if (argc != 1) {
74               napi_throw_error(env, nullptr, "Store args number must be one.");
75               return nullptr;
76           }
77           napi_valuetype type = napi_undefined;
78           napi_typeof(env, args[0], &type);
79           if (type != napi_number) {
80               napi_throw_error(env, nullptr, "Store args is not number.");
81               return nullptr;
82           }
83           if (thisVar == nullptr) {
84               return nullptr;
85           }
86
87           void* object = nullptr;
88           napi_unwrap(env, thisVar, &object);
89           if (object == nullptr) {
90               return nullptr;
91           }
92
93           uint32_t value = 0;
94           napi_get_value_uint32(env, args[0], &value);
95           CustomNativeObject* obj = static_cast<CustomNativeObject*>(object);
96           std::lock_guard<std::mutex> lock(obj->numberSetMutex_);
97           reinterpret_cast<CustomNativeObject *>(object)->numberSet_.insert(value);
98           return nullptr;
99       }
100
101       // Delete an element from the array.
102       static napi_value Erase(napi_env env, napi_callback_info info)
103       {
104           size_t argc = 1;
105           napi_value args[1] = {nullptr};
106           napi_value thisVar = nullptr;
107           napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr);
108           if (argc != 1) {
109               napi_throw_error(env, nullptr, "Erase args number must be one.");
110               return nullptr;
111           }
112           napi_valuetype type = napi_undefined;
113           napi_typeof(env, args[0], &type);
114           if (type != napi_number) {
115               napi_throw_error(env, nullptr, "Erase args is not number.");
116               return nullptr;
117           }
118           if (thisVar == nullptr) {
119               return nullptr;
120           }
121
122           void* object = nullptr;
123           napi_unwrap(env, thisVar, &object);
124           if (object == nullptr) {
125               return nullptr;
126           }
127
128           uint32_t value = 0;
129           napi_get_value_uint32(env, args[0], &value);
130
131           CustomNativeObject* obj = static_cast<CustomNativeObject*>(object);
132           std::lock_guard<std::mutex> lock(obj->numberSetMutex_);
133           reinterpret_cast<CustomNativeObject *>(object)->numberSet_.erase(value);
134           return nullptr;
135       }
136
137       // Clear the array.
138       static napi_value Clear(napi_env env, napi_callback_info info)
139       {
140           napi_value thisVar = nullptr;
141           napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
142           if (thisVar == nullptr) {
143               return nullptr;
144           }
145           void* object = nullptr;
146           napi_unwrap(env, thisVar, &object);
147           if (object == nullptr) {
148               return nullptr;
149           }
150           CustomNativeObject* obj = static_cast<CustomNativeObject*>(object);
151           std::lock_guard<std::mutex> lock(obj->numberSetMutex_);
152           reinterpret_cast<CustomNativeObject *>(object)->numberSet_.clear();
153           return nullptr;
154       }
155
156       // Set the transmission mode.
157       static napi_value SetTransferDetached(napi_env env, napi_callback_info info)
158       {
159           size_t argc = 1;
160           napi_value args[1];
161           napi_value thisVar;
162           napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr);
163           if (argc != 1) {
164               napi_throw_error(env, nullptr, "SetTransferDetached args number must be one.");
165               return nullptr;
166           }
167
168           if (thisVar == nullptr) {
169               return nullptr;
170           }
171
172           napi_valuetype type = napi_undefined;
173           napi_typeof(env, args[0], &type);
174           if (type != napi_boolean) {
175               napi_throw_error(env, nullptr, "SetTransferDetached args is not boolean.");
176               return nullptr;
177           }
178
179           bool isDetached;
180           napi_get_value_bool(env, args[0], &isDetached);
181
182           void* object = nullptr;
183           napi_unwrap(env, thisVar, &object);
184           if (object == nullptr) {
185               return nullptr;
186           }
187           CustomNativeObject* obj = static_cast<CustomNativeObject*>(object);
188           std::lock_guard<std::mutex> lock(obj->numberSetMutex_);
189           obj->isDetached_ = isDetached;
190           return nullptr;
191       }
192
193       bool isDetached_ = false;
194
195   private:
196       CustomNativeObject(const CustomNativeObject &) = delete;
197       CustomNativeObject &operator=(const CustomNativeObject &) = delete;
198
199       std::unordered_set<uint32_t> numberSet_{};
200       std::mutex numberSetMutex_{};
201   };
202
203   void FinializeCallback(napi_env env, void *data, void *hint)
204   {
205       return;
206   }
207
208   // Detach a callback. Generally, it is called in serialization to perform cleanup operations when the object is detached.
209   void* DetachCallback(napi_env env, void *value, void *hint)
210   {
211       if (hint == nullptr) {
212           return value;
213       }
214       napi_value jsObject = nullptr;
215       napi_get_reference_value(env, reinterpret_cast<napi_ref>(hint), &jsObject);
216       void* object = nullptr;
217       if (static_cast<CustomNativeObject*>(value)->isDetached_) {
218           napi_remove_wrap(env, jsObject, &object);
219       }
220       return value;
221   }
222
223   // Attach the callback, which is called during deserialization.
224   napi_value AttachCallback(napi_env env, void* value, void* hint)
225   {
226       napi_value object = nullptr;
227       napi_create_object(env, &object);
228       napi_property_descriptor desc[] = {
229           {"getAddress", nullptr, CustomNativeObject::GetAddress, nullptr, nullptr, nullptr, napi_default, nullptr},
230           {"getSetSize", nullptr, CustomNativeObject::GetSetSize, nullptr, nullptr, nullptr, napi_default, nullptr},
231           {"store", nullptr, CustomNativeObject::Store, nullptr, nullptr, nullptr, napi_default, nullptr},
232           {"erase", nullptr, CustomNativeObject::Erase, nullptr, nullptr, nullptr, napi_default, nullptr},
233           {"clear", nullptr, CustomNativeObject::Clear, nullptr, nullptr, nullptr, napi_default, nullptr}};
234       napi_define_properties(env, object, sizeof(desc) / sizeof(desc[0]), desc);
235       // Bind the ArkTS object's lifecycle of the native object.
236       napi_wrap(env, object, value, FinializeCallback, nullptr, nullptr);
237       // Enable the ArkTS object to carry native information.
238       napi_coerce_to_native_binding_object(env, object, DetachCallback, AttachCallback, value, nullptr);
239       return object;
240   }
241
242   EXTERN_C_START
243   static napi_value Init(napi_env env, napi_value exports)
244   {
245       napi_property_descriptor desc[] = {
246           {"getAddress", nullptr, CustomNativeObject::GetAddress, nullptr, nullptr, nullptr, napi_default, nullptr},
247           {"getSetSize", nullptr, CustomNativeObject::GetSetSize, nullptr, nullptr, nullptr, napi_default, nullptr},
248           {"store", nullptr, CustomNativeObject::Store, nullptr, nullptr, nullptr, napi_default, nullptr},
249           {"erase", nullptr, CustomNativeObject::Erase, nullptr, nullptr, nullptr, napi_default, nullptr},
250           {"clear", nullptr, CustomNativeObject::Clear, nullptr, nullptr, nullptr, napi_default, nullptr},
251           {"setTransferDetached", nullptr, CustomNativeObject::SetTransferDetached, nullptr, nullptr, nullptr, napi_default, nullptr}};
252       napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
253       auto &object = CustomNativeObject::GetInstance();
254       napi_wrap(env, exports, reinterpret_cast<void*>(&object), FinializeCallback, nullptr, nullptr);
255       napi_ref exportsRef;
256       napi_create_reference(env, exports, 1, &exportsRef);
257       napi_coerce_to_native_binding_object(env, exports, DetachCallback, AttachCallback, reinterpret_cast<void*>(&object), exportsRef);
258       return exports;
259   }
260   EXTERN_C_END
261
262   static napi_module demoModule = {
263       .nm_version = 1,
264       .nm_flags = 0,
265       .nm_filename = nullptr,
266       .nm_register_func = Init,
267       .nm_modname = "entry",
268       .nm_priv = ((void*)0),
269       .reserved = { 0 },
270   };
271
272   extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
273   {
274       napi_module_register(&demoModule);
275   }
276   ```
277
278
2792. Declare the APIs in ArkTS.
280
281   ```ts
282   // Index.d.ts
283   export const getAddress: () => number;
284   export const getSetSize: () => number;
285   export const store: (a: number) => void;
286   export const erase: (a: number) => void;
287   export const clear: () => void;
288   export const setTransferDetached: (b : boolean) => number;
289   ```
290
2913. Call native functions from ArkTS objects.
292
293   In transfer mode, after cross-thread transfer, the original ArkTS object is unbound from the native object and can no longer access it. The following is an example:
294   ```ts
295   import testNapi from 'libentry.so';
296   import { taskpool } from '@kit.ArkTS';
297
298   @Concurrent
299   function getAddress() {
300     let address: number = testNapi.getAddress();
301     console.info("taskpool:: address is " + address);
302   }
303
304   @Concurrent
305   function store(a:number, b:number, c:number) {
306     let size:number = testNapi.getSetSize();
307     console.info("set size is " + size + " before store");
308     testNapi.store(a);
309     testNapi.store(b);
310     testNapi.store(c);
311     size = testNapi.getSetSize();
312     console.info("set size is " + size + " after store");
313   }
314
315   @Concurrent
316   function erase(a:number) {
317     let size:number = testNapi.getSetSize();
318     console.info("set size is " + size + " before erase");
319     testNapi.erase(a);
320     size = testNapi.getSetSize();
321     console.info("set size is " + size + " after erase");
322   }
323
324   @Concurrent
325   function clear() {
326     let size:number = testNapi.getSetSize();
327     console.info("set size is " + size + " before clear");
328     testNapi.clear();
329     size = testNapi.getSetSize();
330     console.info("set size is " + size + " after clear");
331   }
332
333   // Transfer mode.
334   async function test(): Promise<void> {
335     // Set setTransferDetached to true to enable the transfer mode.
336     testNapi.setTransferDetached(true);
337     let address:number = testNapi.getAddress();
338     console.info("host thread address is " + address);
339
340     let task1 = new taskpool.Task(getAddress, testNapi);
341     await taskpool.execute(task1);
342
343     let task2 = new taskpool.Task(store, 1, 2, 3);
344     await taskpool.execute(task2);
345
346     let task3 = new taskpool.Task(store, 4, 5, 6);
347     await taskpool.execute(task3);
348
349     // In transfer mode, after testNapi is transferred across threads, the main thread can no longer access the value of the native object.
350     let size:number = testNapi.getSetSize();
351     // The output log is "host thread size is undefined".
352     console.info("host thread size is " + size);
353
354     let task4 = new taskpool.Task(erase, 3);
355     await taskpool.execute(task4);
356
357     let task5 = new taskpool.Task(erase, 5);
358     await taskpool.execute(task5);
359
360     let task6 = new taskpool.Task(clear);
361     await taskpool.execute(task6);
362   }
363
364   @Entry
365   @Component
366   struct Index {
367     @State message: string = 'Hello World';
368
369     build() {
370       Row() {
371         Column() {
372           Text(this.message)
373             .fontSize($r('app.float.page_text_font_size'))
374             .fontWeight(FontWeight.Bold)
375             .onClick(() => {
376               test();
377             })
378         }
379         .width('100%')
380       }
381       .height('100%')
382     }
383   }
384   ```
385
386   In shared mode, the original ArkTS object retains access to the native object after cross-thread transfer. The following is an example:
387   ```ts
388   import testNapi from 'libentry.so';
389   import { taskpool } from '@kit.ArkTS';
390
391   @Concurrent
392   function getAddress() {
393     let address: number = testNapi.getAddress();
394     console.info("taskpool:: address is " + address);
395   }
396
397   @Concurrent
398   function store(a:number, b:number, c:number) {
399     let size:number = testNapi.getSetSize();
400     console.info("set size is " + size + " before store");
401     testNapi.store(a);
402     testNapi.store(b);
403     testNapi.store(c);
404     size = testNapi.getSetSize();
405     console.info("set size is " + size + " after store");
406   }
407
408   @Concurrent
409   function erase(a:number) {
410     let size:number = testNapi.getSetSize();
411     console.info("set size is " + size + " before erase");
412     testNapi.erase(a);
413     size = testNapi.getSetSize();
414     console.info("set size is " + size + " after erase");
415   }
416
417   @Concurrent
418   function clear() {
419     let size:number = testNapi.getSetSize();
420     console.info("set size is " + size + " before clear");
421     testNapi.clear();
422     size = testNapi.getSetSize();
423     console.info("set size is " + size + " after clear");
424   }
425
426   // Shared mode.
427   async function test(): Promise<void> {
428     let address:number = testNapi.getAddress();
429     console.info("host thread address is " + address);
430
431     let task1 = new taskpool.Task(getAddress, testNapi);
432     await taskpool.execute(task1);
433
434     let task2 = new taskpool.Task(store, 1, 2, 3);
435     await taskpool.execute(task2);
436
437     let task3 = new taskpool.Task(store, 4, 5, 6);
438     await taskpool.execute(task3);
439
440     // In shared mode, after testNapi is transferred across threads, the main thread can continue to access the value of the native object.
441     let size:number = testNapi.getSetSize();
442     // The output log is "host thread size is 6".
443     console.info("host thread size is " + size);
444
445     let task4 = new taskpool.Task(erase, 3);
446     await taskpool.execute(task4);
447
448     let task5 = new taskpool.Task(erase, 5);
449     await taskpool.execute(task5);
450
451     let task6 = new taskpool.Task(clear);
452     await taskpool.execute(task6);
453   }
454
455   @Entry
456   @Component
457   struct Index {
458     @State message: string = 'Hello World';
459
460     build() {
461       Row() {
462         Column() {
463           Text(this.message)
464             .fontSize($r('app.float.page_text_font_size'))
465             .fontWeight(FontWeight.Bold)
466             .onClick(() => {
467               test();
468             })
469         }
470         .width('100%')
471       }
472       .height('100%')
473     }
474   }
475   ```
476