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