1# 自定义Native Transferable对象的多线程操作场景 2<!--Kit: ArkTS--> 3<!--Subsystem: CommonLibrary--> 4<!--Owner: @lijiamin2025--> 5<!--Designer: @weng-changcheng--> 6<!--Tester: @kirl75; @zsw_zhushiwei--> 7<!--Adviser: @ge-yafang--> 8 9在ArkTS应用开发中,有很多场景需要将ArkTS对象与Native对象进行绑定。ArkTS对象将数据写入Native对象,Native对象再将数据写入目的地。例如,将ArkTS对象中的数据写入C++数据库场景。 10 11Native Transferable对象有两种模式:共享模式和转移模式。本示例将详细说明如何实现这两种模式。 12 131. Native实现各项功能。 14 15 ```cpp 16 // napi_init.cpp 17 #include <mutex> 18 #include <unordered_set> 19 #include "napi/native_api.h" 20 #include <hilog/log.h> 21 22 class CustomNativeObject { 23 public: 24 CustomNativeObject() {} 25 ~CustomNativeObject() = default; 26 static CustomNativeObject& GetInstance() 27 { 28 static CustomNativeObject instance; 29 return instance; 30 } 31 32 static napi_value GetAddress(napi_env env, napi_callback_info info) 33 { 34 napi_value thisVar = nullptr; 35 napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr); 36 if (thisVar == nullptr) { 37 return nullptr; 38 } 39 void* object = nullptr; 40 napi_unwrap(env, thisVar, &object); 41 if (object == nullptr) { 42 return nullptr; 43 } 44 45 uint64_t addressVal = reinterpret_cast<uint64_t>(object); 46 napi_value address = nullptr; 47 napi_create_bigint_uint64(env, addressVal, &address); 48 return address; 49 } 50 51 // 获取数组大小 52 static napi_value GetSetSize(napi_env env, napi_callback_info info) 53 { 54 napi_value thisVar = nullptr; 55 napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr); 56 if (thisVar == nullptr) { 57 return nullptr; 58 } 59 void* object = nullptr; 60 napi_unwrap(env, thisVar, &object); 61 if (object == nullptr) { 62 return nullptr; 63 } 64 CustomNativeObject* obj = static_cast<CustomNativeObject*>(object); 65 std::lock_guard<std::mutex> lock(obj->numberSetMutex_); 66 uint32_t setSize = reinterpret_cast<CustomNativeObject*>(object)->numberSet_.size(); 67 napi_value napiSize = nullptr; 68 napi_create_uint32(env, setSize, &napiSize); 69 return napiSize; 70 } 71 72 // 往数组里插入元素 73 static napi_value Store(napi_env env, napi_callback_info info) 74 { 75 size_t argc = 1; 76 napi_value args[1] = {nullptr}; 77 napi_value thisVar = nullptr; 78 napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr); 79 if (argc != 1) { 80 napi_throw_error(env, nullptr, "Store args number must be one."); 81 return nullptr; 82 } 83 napi_valuetype type = napi_undefined; 84 napi_typeof(env, args[0], &type); 85 if (type != napi_number) { 86 napi_throw_error(env, nullptr, "Store args is not number."); 87 return nullptr; 88 } 89 if (thisVar == nullptr) { 90 return nullptr; 91 } 92 93 void* object = nullptr; 94 napi_unwrap(env, thisVar, &object); 95 if (object == nullptr) { 96 return nullptr; 97 } 98 99 uint32_t value = 0; 100 napi_get_value_uint32(env, args[0], &value); 101 CustomNativeObject* obj = static_cast<CustomNativeObject*>(object); 102 std::lock_guard<std::mutex> lock(obj->numberSetMutex_); 103 reinterpret_cast<CustomNativeObject *>(object)->numberSet_.insert(value); 104 return nullptr; 105 } 106 107 // 删除数组元素 108 static napi_value Erase(napi_env env, napi_callback_info info) 109 { 110 size_t argc = 1; 111 napi_value args[1] = {nullptr}; 112 napi_value thisVar = nullptr; 113 napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr); 114 if (argc != 1) { 115 napi_throw_error(env, nullptr, "Erase args number must be one."); 116 return nullptr; 117 } 118 napi_valuetype type = napi_undefined; 119 napi_typeof(env, args[0], &type); 120 if (type != napi_number) { 121 napi_throw_error(env, nullptr, "Erase args is not number."); 122 return nullptr; 123 } 124 if (thisVar == nullptr) { 125 return nullptr; 126 } 127 128 void* object = nullptr; 129 napi_unwrap(env, thisVar, &object); 130 if (object == nullptr) { 131 return nullptr; 132 } 133 134 uint32_t value = 0; 135 napi_get_value_uint32(env, args[0], &value); 136 137 CustomNativeObject* obj = static_cast<CustomNativeObject*>(object); 138 std::lock_guard<std::mutex> lock(obj->numberSetMutex_); 139 reinterpret_cast<CustomNativeObject *>(object)->numberSet_.erase(value); 140 return nullptr; 141 } 142 143 // 清空数组 144 static napi_value Clear(napi_env env, napi_callback_info info) 145 { 146 napi_value thisVar = nullptr; 147 napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr); 148 if (thisVar == nullptr) { 149 return nullptr; 150 } 151 void* object = nullptr; 152 napi_unwrap(env, thisVar, &object); 153 if (object == nullptr) { 154 return nullptr; 155 } 156 CustomNativeObject* obj = static_cast<CustomNativeObject*>(object); 157 std::lock_guard<std::mutex> lock(obj->numberSetMutex_); 158 reinterpret_cast<CustomNativeObject *>(object)->numberSet_.clear(); 159 return nullptr; 160 } 161 162 // 设置传输模式 163 static napi_value SetTransferDetached(napi_env env, napi_callback_info info) 164 { 165 size_t argc = 1; 166 napi_value args[1]; 167 napi_value thisVar; 168 napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr); 169 if (argc != 1) { 170 napi_throw_error(env, nullptr, "SetTransferDetached args number must be one."); 171 return nullptr; 172 } 173 174 if (thisVar == nullptr) { 175 return nullptr; 176 } 177 178 napi_valuetype type = napi_undefined; 179 napi_typeof(env, args[0], &type); 180 if (type != napi_boolean) { 181 napi_throw_error(env, nullptr, "SetTransferDetached args is not boolean."); 182 return nullptr; 183 } 184 185 bool isDetached; 186 napi_get_value_bool(env, args[0], &isDetached); 187 188 void* object = nullptr; 189 napi_unwrap(env, thisVar, &object); 190 if (object == nullptr) { 191 return nullptr; 192 } 193 CustomNativeObject* obj = static_cast<CustomNativeObject*>(object); 194 std::lock_guard<std::mutex> lock(obj->numberSetMutex_); 195 obj->isDetached_ = isDetached; 196 return nullptr; 197 } 198 199 bool isDetached_ = false; 200 201 private: 202 CustomNativeObject(const CustomNativeObject &) = delete; 203 CustomNativeObject &operator=(const CustomNativeObject &) = delete; 204 205 std::unordered_set<uint32_t> numberSet_{}; 206 std::mutex numberSetMutex_{}; 207 }; 208 209 void FinializeCallback(napi_env env, void *data, void *hint) 210 { 211 return; 212 } 213 214 // 解绑回调,在序列化时调用,可在对象解绑时执行一些清理操作 215 void* DetachCallback(napi_env env, void *value, void *hint) 216 { 217 if (hint == nullptr) { 218 return value; 219 } 220 napi_value jsObject = nullptr; 221 napi_get_reference_value(env, reinterpret_cast<napi_ref>(hint), &jsObject); 222 void* object = nullptr; 223 if (static_cast<CustomNativeObject*>(value)->isDetached_) { 224 napi_remove_wrap(env, jsObject, &object); 225 } 226 return value; 227 } 228 229 // 绑定回调,在反序列化时调用 230 napi_value AttachCallback(napi_env env, void* value, void* hint) 231 { 232 napi_value object = nullptr; 233 napi_create_object(env, &object); 234 napi_property_descriptor desc[] = { 235 {"getAddress", nullptr, CustomNativeObject::GetAddress, nullptr, nullptr, nullptr, napi_default, nullptr}, 236 {"getSetSize", nullptr, CustomNativeObject::GetSetSize, nullptr, nullptr, nullptr, napi_default, nullptr}, 237 {"store", nullptr, CustomNativeObject::Store, nullptr, nullptr, nullptr, napi_default, nullptr}, 238 {"erase", nullptr, CustomNativeObject::Erase, nullptr, nullptr, nullptr, napi_default, nullptr}, 239 {"clear", nullptr, CustomNativeObject::Clear, nullptr, nullptr, nullptr, napi_default, nullptr}}; 240 napi_define_properties(env, object, sizeof(desc) / sizeof(desc[0]), desc); 241 // 将JS对象object和native对象value生命周期进行绑定 242 napi_wrap(env, object, value, FinializeCallback, nullptr, nullptr); 243 // JS对象携带native信息 244 napi_coerce_to_native_binding_object(env, object, DetachCallback, AttachCallback, value, nullptr); 245 return object; 246 } 247 248 EXTERN_C_START 249 static napi_value Init(napi_env env, napi_value exports) 250 { 251 napi_property_descriptor desc[] = { 252 {"getAddress", nullptr, CustomNativeObject::GetAddress, nullptr, nullptr, nullptr, napi_default, nullptr}, 253 {"getSetSize", nullptr, CustomNativeObject::GetSetSize, nullptr, nullptr, nullptr, napi_default, nullptr}, 254 {"store", nullptr, CustomNativeObject::Store, nullptr, nullptr, nullptr, napi_default, nullptr}, 255 {"erase", nullptr, CustomNativeObject::Erase, nullptr, nullptr, nullptr, napi_default, nullptr}, 256 {"clear", nullptr, CustomNativeObject::Clear, nullptr, nullptr, nullptr, napi_default, nullptr}, 257 {"setTransferDetached", nullptr, CustomNativeObject::SetTransferDetached, nullptr, nullptr, nullptr, napi_default, nullptr}}; 258 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 259 auto &object = CustomNativeObject::GetInstance(); 260 napi_wrap(env, exports, reinterpret_cast<void*>(&object), FinializeCallback, nullptr, nullptr); 261 napi_ref exportsRef; 262 napi_create_reference(env, exports, 1, &exportsRef); 263 napi_coerce_to_native_binding_object(env, exports, DetachCallback, AttachCallback, reinterpret_cast<void*>(&object), exportsRef); 264 return exports; 265 } 266 EXTERN_C_END 267 268 static napi_module demoModule = { 269 .nm_version = 1, 270 .nm_flags = 0, 271 .nm_filename = nullptr, 272 .nm_register_func = Init, 273 .nm_modname = "entry", 274 .nm_priv = ((void*)0), 275 .reserved = { 0 }, 276 }; 277 278 extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 279 { 280 napi_module_register(&demoModule); 281 } 282 ``` 283 284 2852. 在ArkTS中声明接口。 286 287 ```ts 288 // Index.d.ts 289 export const getAddress: () => number; 290 export const getSetSize: () => number; 291 export const store: (a: number) => void; 292 export const erase: (a: number) => void; 293 export const clear: () => void; 294 export const setTransferDetached: (b : boolean) => number; 295 ``` 296 2973. ArkTS对象调用Native侧实现的各项功能。 298 299 在转移模式下,跨线程传递后,原来的ArkTS对象与Native对象解绑,因此不能继续访问。示例如下: 300 ```ts 301 import testNapi from 'libentry.so'; 302 import { taskpool } from '@kit.ArkTS'; 303 304 @Concurrent 305 function getAddress() { 306 let address: number = testNapi.getAddress(); 307 console.info("taskpool:: address is " + address); 308 } 309 310 @Concurrent 311 function store(a:number, b:number, c:number) { 312 let size:number = testNapi.getSetSize(); 313 console.info("set size is " + size + " before store"); 314 testNapi.store(a); 315 testNapi.store(b); 316 testNapi.store(c); 317 size = testNapi.getSetSize(); 318 console.info("set size is " + size + " after store"); 319 } 320 321 @Concurrent 322 function erase(a:number) { 323 let size:number = testNapi.getSetSize(); 324 console.info("set size is " + size + " before erase"); 325 testNapi.erase(a); 326 size = testNapi.getSetSize(); 327 console.info("set size is " + size + " after erase"); 328 } 329 330 @Concurrent 331 function clear() { 332 let size:number = testNapi.getSetSize(); 333 console.info("set size is " + size + " before clear"); 334 testNapi.clear(); 335 size = testNapi.getSetSize(); 336 console.info("set size is " + size + " after clear"); 337 } 338 339 // 转移模式 340 async function test(): Promise<void> { 341 // setTransferDetached 设置为true,表示传输方式为转移模式 342 testNapi.setTransferDetached(true); 343 let address:number = testNapi.getAddress(); 344 console.info("host thread address is " + address); 345 346 let task1 = new taskpool.Task(getAddress, testNapi); 347 await taskpool.execute(task1); 348 349 let task2 = new taskpool.Task(store, 1, 2, 3); 350 await taskpool.execute(task2); 351 352 let task3 = new taskpool.Task(store, 4, 5, 6); 353 await taskpool.execute(task3); 354 355 // 由于已经设置了转移模式,且testNapi已跨线程传递,所以主线程无法继续访问到Native对象的值 356 let size:number = testNapi.getSetSize(); 357 // 输出的日志为“host thread size is undefined” 358 console.info("host thread size is " + size); 359 360 let task4 = new taskpool.Task(erase, 3); 361 await taskpool.execute(task4); 362 363 let task5 = new taskpool.Task(erase, 5); 364 await taskpool.execute(task5); 365 366 let task6 = new taskpool.Task(clear); 367 await taskpool.execute(task6); 368 } 369 370 @Entry 371 @Component 372 struct Index { 373 @State message: string = 'Hello World'; 374 375 build() { 376 Row() { 377 Column() { 378 Text(this.message) 379 .fontSize($r('app.float.page_text_font_size')) 380 .fontWeight(FontWeight.Bold) 381 .onClick(() => { 382 test(); 383 }) 384 } 385 .width('100%') 386 } 387 .height('100%') 388 } 389 } 390 ``` 391 392 在共享模式下,跨线程传递后,原来的ArkTS对象还可以继续访问Native对象。示例如下: 393 ```ts 394 // Index.ets 395 import testNapi from 'libentry.so'; 396 import { taskpool } from '@kit.ArkTS'; 397 398 @Concurrent 399 function getAddress() { 400 let address: number = testNapi.getAddress(); 401 console.info("taskpool:: address is " + address); 402 } 403 404 @Concurrent 405 function store(a:number, b:number, c:number) { 406 let size:number = testNapi.getSetSize(); 407 console.info("set size is " + size + " before store"); 408 testNapi.store(a); 409 testNapi.store(b); 410 testNapi.store(c); 411 size = testNapi.getSetSize(); 412 console.info("set size is " + size + " after store"); 413 } 414 415 @Concurrent 416 function erase(a:number) { 417 let size:number = testNapi.getSetSize(); 418 console.info("set size is " + size + " before erase"); 419 testNapi.erase(a); 420 size = testNapi.getSetSize(); 421 console.info("set size is " + size + " after erase"); 422 } 423 424 @Concurrent 425 function clear() { 426 let size:number = testNapi.getSetSize(); 427 console.info("set size is " + size + " before clear"); 428 testNapi.clear(); 429 size = testNapi.getSetSize(); 430 console.info("set size is " + size + " after clear"); 431 } 432 433 // 共享模式 434 async function test(): Promise<void> { 435 let address:number = testNapi.getAddress(); 436 console.info("host thread address is " + address); 437 438 let task1 = new taskpool.Task(getAddress, testNapi); 439 await taskpool.execute(task1); 440 441 let task2 = new taskpool.Task(store, 1, 2, 3); 442 await taskpool.execute(task2); 443 444 let task3 = new taskpool.Task(store, 4, 5, 6); 445 await taskpool.execute(task3); 446 447 // 由于默认的传输模式为共享模式,testNapi跨线程传递后,主线程可以继续访问Native对象的值 448 let size:number = testNapi.getSetSize(); 449 // 输出的日志为“host thread size is 6” 450 console.info("host thread size is " + size); 451 452 let task4 = new taskpool.Task(erase, 3); 453 await taskpool.execute(task4); 454 455 let task5 = new taskpool.Task(erase, 5); 456 await taskpool.execute(task5); 457 458 let task6 = new taskpool.Task(clear); 459 await taskpool.execute(task6); 460 } 461 462 @Entry 463 @Component 464 struct Index { 465 @State message: string = 'Hello World'; 466 467 build() { 468 Row() { 469 Column() { 470 Text(this.message) 471 .fontSize($r('app.float.page_text_font_size')) 472 .fontWeight(FontWeight.Bold) 473 .onClick(() => { 474 test(); 475 }) 476 } 477 .width('100%') 478 } 479 .height('100%') 480 } 481 } 482 ```