1# Using Native APIs in Application Projects 2 3OpenHarmony applications use JavaScript (JS) when calling native APIs. The native APIs (NAPIs) provided by the [ace_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository are used to implement interaction with JS. The names of the NAPIs are the same as those in the third-party **Node.js**. For details about the interfaces supported, see **libnapi.ndk.json** in the ace_napi repository. 4 5## How to Develop 6 7The DevEco Studio has a default project that uses NAPIs. You can choose **File** > **New** > **Create Project** to create a **Native C++** project. The **cpp** directory is generated in the **main** directory. You can use the NAPIs provided by the **ace_napi** repository for development. 8 9You can import the native .so that contains the JS processing logic. For example, **import hello from 'libhello.so'** to use the **libhello.so** capability. Then, the JS object created using the NAPI can be passed to the **hello** object of the application to call the native capability. 10 11## Development Guidelines 12 13### Registration 14 15* Add **static** to the **nm_register_func** function to prevent symbol conflicts with other .so files. 16* The name of the module registration entry, that is, the function decorated by **\_\_attribute\_\_((constructor))**, must be unique. 17 18### .so Naming Rules 19 20The .so file names must comply with the following rules: 21 22* Each module has a .so file. 23* The **nm_modname** field in **napi_module** must be the same as the module name. For example, if the module name is **hello**, name the .so file **libhello.so**. The sample code for importing the .so file is **import hello from 'libhello.so'**. 24 25### JS Objects and Threads 26 27The Ark engine prevents NAPIs from being called to operate JS objects in non-JS threads. Otherwise, the application will crash. Observe the following rules: 28 29* The NAPIs can be used only in JS threads. 30* **env** is bound to a thread and cannot be used across threads. The JS object created by a NAPI can be used only in the thread, in which the object is created, that is, the JS object is bound to the **env** of the thread. 31 32### Importing Header Files 33 34Before using NAPI objects and methods, include **napi/native_api.h**. Otherwise, if only the third-party library header file is included, an error will be reporting, indicating that the interface cannot be found. 35 36### napi_create_async_work 37 38**napi_create_async_work** has two callbacks: 39 40* **execute**: processes service logic asynchronously. This callback is not executed by a JS thread; therefore, it cannot call any NAPI. The return value of **execute** is processed by the **complete** callback. 41 42* **complete**: calls the NAPI to encapsulate the return value of **execute** into a JS object and return it for processing. This callback is executed by a JS thread. 43 44```c++ 45napi_status napi_create_async_work(napi_env env, 46 napi_value async_resource, 47 napi_value async_resource_name, 48 napi_async_execute_callback execute, 49 napi_async_complete_callback complete, 50 void* data, 51 napi_async_work* result) 52``` 53 54 55 56## Encapsulating Synchronous and Asynchronous APIs for the Storage Module 57 58### Overview 59 60This example shows how to encapsulate the synchronous and asynchronous APIs of the **Storage** module. The **Storage** module implements the functions of storing, obtaining, deleting, and clearing data. 61 62### API Declaration 63 64```typescript 65import { AsyncCallback } from './basic'; 66declare namespace storage { 67 function get(key: string, callback: AsyncCallback<string>): void; 68 function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void; 69 function get(key: string, defaultValue?: string): Promise<string>; 70 function set(key: string, value: string, callback: AsyncCallback<string>): void; 71 function remove(key: string, callback: AsyncCallback<void>): void; 72 function clear(callback: AsyncCallback<void>): void; 73 function getSync(key: string, defaultValue?: string): string; 74 function setSync(key: string, value: string): void; 75 function removeSync(key: string): void; 76 function clearClear(): void; 77} 78export default storage; 79``` 80 81 82 83### Implementation 84 85You can obtain the complete code from sample/native_module_storage/ in the [OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository. 86 87**1. Register the module.** 88 89Register four synchronous APIs (**getSync**, **setSync**, **removeSync**, and **clearSync**) and four asynchronous APIs (**get**, **set**, **remove**, and **clear**). 90 91```c++ 92/*********************************************** 93 * Module export and register 94 ***********************************************/ 95static napi_value StorageExport(napi_env env, napi_value exports) 96{ 97 napi_property_descriptor desc[] = { 98 DECLARE_NAPI_FUNCTION("get", JSStorageGet), 99 DECLARE_NAPI_FUNCTION("set", JSStorageSet), 100 DECLARE_NAPI_FUNCTION("remove", JSStorageDelete), 101 DECLARE_NAPI_FUNCTION("clear", JSStorageClear), 102 103 DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync), 104 DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync), 105 DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync), 106 DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync), 107 }; 108 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); 109 return exports; 110} 111 112// Storage module 113static napi_module storage_module = {.nm_version = 1, 114 .nm_flags = 0, 115 .nm_filename = nullptr, 116 .nm_register_func = StorageExport, 117 .nm_modname = "storage", 118 .nm_priv = ((void*)0), 119 .reserved = {0}}; 120 121// Register the storage module 122extern "C" __attribute__((constructor)) void StorageRegister() 123{ 124 napi_module_register(&storage_module); 125} 126``` 127 128**2. Implement getSync.** 129 130The **getSync** function registered for the **Storage** module is **JSStorageGetSync**. Obtain data from **gKeyValueStorage**, create a string object, and return the object created. 131 132```c 133static napi_value JSStorageGetSync(napi_env env, napi_callback_info info) 134{ 135 GET_PARAMS(env, info, 2); 136 NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); 137 char key[32] = {0}; 138 size_t keyLen = 0; 139 char value[128] = {0}; 140 size_t valueLen = 0; 141 142 // Parse parameters. 143 for (size_t i = 0; i < argc; i++) { 144 napi_valuetype valueType; 145 napi_typeof(env, argv[i], &valueType); 146 147 if (i == 0 && valueType == napi_string) { 148 napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen); 149 } else if (i == 1 && valueType == napi_string) { 150 napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen); 151 break; 152 } else { 153 NAPI_ASSERT(env, false, "type mismatch"); 154 } 155 } 156 157 // Service logic for obtaining data. This example simply obtains data from a global variable. 158 auto itr = gKeyValueStorage.find(key); 159 napi_value result = nullptr; 160 if (itr != gKeyValueStorage.end()) { 161 // Use the data obtained to create a JS object of the string type. 162 napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result); 163 } else if (valueLen > 0) { 164 // If no data is obtained, use the default value to create a JS object. 165 napi_create_string_utf8(env, value, valueLen, &result); 166 } else { 167 NAPI_ASSERT(env, false, "key does not exist"); 168 } 169 // Return the result. 170 return result; 171} 172``` 173 174**3. Implement get().** 175 176The **get** function registered for the **Storage** module is **JSStorageGet**. 177 178```c 179static napi_value JSStorageGet(napi_env env, napi_callback_info info) 180{ 181 GET_PARAMS(env, info, 3); 182 NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); 183 184 // StorageAsyncContext is a custom class used to store data during execution. 185 StorageAsyncContext* asyncContext = new StorageAsyncContext(); 186 187 asyncContext->env = env; 188 189 // Obtain parameters. 190 for (size_t i = 0; i < argc; i++) { 191 napi_valuetype valueType; 192 napi_typeof(env, argv[i], &valueType); 193 194 if (i == 0 && valueType == napi_string) { 195 napi_get_value_string_utf8(env, argv[i], asyncContext->key, 31, &asyncContext->keyLen); 196 } else if (i == 1 && valueType == napi_string) { 197 napi_get_value_string_utf8(env, argv[i], asyncContext->value, 127, &asyncContext->valueLen); 198 } else if (i == 1 && valueType == napi_function) { 199 napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef); 200 break; 201 } else if (i == 2 && valueType == napi_function) { 202 napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef); 203 } else { 204 NAPI_ASSERT(env, false, "type mismatch"); 205 } 206 } 207 208 napi_value result = nullptr; 209 210 // Determine whether promise or callback is used based on the parameters. 211 if (asyncContext->callbackRef == nullptr) { 212 // Create a promise. 213 napi_create_promise(env, &asyncContext->deferred, &result); 214 } else { 215 napi_get_undefined(env, &result); 216 } 217 218 napi_value resource = nullptr; 219 napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource); 220 221 napi_create_async_work( 222 env, nullptr, resource, 223 // Callback 1: This callback contains the service logic to be asynchronously executed and is asynchronously executed by the NAPI. Do not operate JS objects using the NAPI because the execution is asynchronous. 224 [](napi_env env, void* data) { 225 StorageAsyncContext* asyncContext = (StorageAsyncContext*)data; 226 auto itr = gKeyValueStorage.find(asyncContext->key); 227 if (itr != gKeyValueStorage.end()) { 228 strncpy_s(asyncContext->value, 127, itr->second.c_str(), itr->second.length()); 229 asyncContext->status = 0; 230 } else { 231 asyncContext->status = 1; 232 } 233 }, 234 // Callback 2: This callback is invoked after callback 1 is complete. The JS thread invokes the callback passed in. 235 [](napi_env env, napi_status status, void* data) { 236 StorageAsyncContext* asyncContext = (StorageAsyncContext*)data; 237 napi_value result[2] = {0}; 238 if (!asyncContext->status) { 239 napi_get_undefined(env, &result[0]); 240 napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]); 241 } else { 242 napi_value message = nullptr; 243 napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message); 244 napi_create_error(env, nullptr, message, &result[0]); 245 napi_get_undefined(env, &result[1]); 246 } 247 if (asyncContext->deferred) { 248 // If a promise is used, check the result of callback 1. 249 if (!asyncContext->status) { 250 // Triggered when callback 1 is successful (status is 1), that is, to invoke the callback passed in then in the promise. 251 napi_resolve_deferred(env, asyncContext->deferred, result[1]); 252 } else { 253 // Triggered when callback 1 fails (status is 0), that is, to invoke the callback passed in catch in the promise. 254 napi_reject_deferred(env, asyncContext->deferred, result[0]); 255 } 256 } else { 257 // If a callback is used, use napi_call_function to invoke the callback to return the result. 258 napi_value callback = nullptr; 259 napi_value returnVal; 260 napi_get_reference_value(env, asyncContext->callbackRef, &callback); 261 napi_call_function(env, nullptr, callback, 2, result, &returnVal); 262 napi_delete_reference(env, asyncContext->callbackRef); 263 } 264 napi_delete_async_work(env, asyncContext->work); 265 delete asyncContext; 266 }, 267 (void*)asyncContext, &asyncContext->work); 268 napi_queue_async_work(env, asyncContext->work); 269 270 return result; 271} 272``` 273 274**JS Sample Code** 275 276```js 277import storage from 'libstorage.so'; 278 279export default { 280 testGetSync() { 281 const name = storage.getSync('name'); 282 console.log('name is ' + name); 283 }, 284 testGet() { 285 storage.get('name') 286 .then(date => { 287 console.log('name is ' + data); 288 }) 289 .catch(error => { 290 console.log('error: ' + error); 291 }); 292 } 293} 294``` 295 296 297 298## Binding Native and JS Objects for the NetServer Module 299 300### Overview 301 302This example shows how to implement the **on**, **off**, and **once** methods and bind C++ and JS objects using **wrap()**. The **NetServer** module implements the network service. 303 304### API Declaration 305 306```typescript 307export class NetServer { 308 function start(port: number): void; 309 function stop(): void; 310 function on('start' | 'stop', callback: Function): void; 311 function once('start' | 'stop', callback: Function): void; 312 function off('start' | 'stop', callback: Function): void; 313} 314``` 315 316### Implementation 317 318You can obtain the complete code from **sample/native_module_netserver/** in the [OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository. 319 320**1. Register the module.** 321 322```c 323static napi_value NetServer::Export(napi_env env, napi_value exports) 324{ 325 const char className[] = "NetServer"; 326 napi_property_descriptor properties[] = { 327 DECLARE_NAPI_FUNCTION("start", JS_Start), 328 DECLARE_NAPI_FUNCTION("stop", JS_Stop), 329 DECLARE_NAPI_FUNCTION("on", JS_On), 330 DECLARE_NAPI_FUNCTION("once", JS_Once), 331 DECLARE_NAPI_FUNCTION("off", JS_Off), 332 }; 333 napi_value netServerClass = nullptr; 334 335 napi_define_class(env, className, sizeof(className), JS_Constructor, nullptr, countof(properties), properties, 336 &netServerClass); 337 338 napi_set_named_property(env, exports, "NetServer", netServerClass); 339 340 return exports; 341} 342``` 343 344**2. Bind C++ and JS objects in a constructor.** 345 346```c 347napi_value NetServer::JS_Constructor(napi_env env, napi_callback_info cbinfo) 348{ 349 napi_value thisVar = nullptr; 350 void* data = nullptr; 351 napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, &data); 352 353 // C++ native object to be mapped to the JS object. 354 NetServer* netServer = new NetServer(env, thisVar); 355 356 // Use napi_wrap to bind netServer and thisVar (JS object created). 357 napi_wrap( 358 env, thisVar, netServer, 359 // The JS object is automatically released by the engine. When the JS object is released, the callback is triggered to release netServer. 360 [](napi_env env, void* data, void* hint) { 361 printf("NetServer::Destructor\n"); 362 NetServer* netServer = (NetServer*)data; 363 delete netServer; 364 }, 365 nullptr, nullptr); 366 367 return thisVar; 368} 369``` 370 371**3. Obtain a C++ object from a JS object.** 372 373```c 374napi_value NetServer::JS_Start(napi_env env, napi_callback_info cbinfo) 375{ 376 size_t argc = 1; 377 napi_value argv[1] = {0}; 378 napi_value thisVar = nullptr; 379 void* data = nullptr; 380 napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data); 381 382 NetServer* netServer = nullptr; 383 // Obtain the C++ object from thisVar using napi_unwrap. 384 napi_unwrap(env, thisVar, (void**)&netServer); 385 386 NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); 387 388 napi_valuetype valueType; 389 napi_typeof(env, argv[0], &valueType); 390 NAPI_ASSERT(env, valueType == napi_number, "type mismatch for parameter 1"); 391 392 int32_t port = 0; 393 napi_get_value_int32(env, argv[0], &port); 394 395 // Start the netServer service. 396 netServer->Start(port); 397 398 napi_value result = nullptr; 399 napi_get_undefined(env, &result); 400 return result; 401} 402``` 403 404After **netServer->Start** is executed, call back the **start** event registered by **on()**. 405 406```c 407int NetServer::Start(int port) 408{ 409 printf("NetServer::Start thread_id: %ld \n", uv_thread_self()); 410 411 struct sockaddr_in addr; 412 int r; 413 414 uv_ip4_addr("0.0.0.0", port, &addr); 415 416 r = uv_tcp_init(loop_, &tcpServer_); 417 if (r) { 418 fprintf(stderr, "Socket creation error\n"); 419 return 1; 420 } 421 422 r = uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0); 423 if (r) { 424 fprintf(stderr, "Bind error\n"); 425 return 1; 426 } 427 428 r = uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection); 429 if (r) { 430 fprintf(stderr, "Listen error %s\n", uv_err_name(r)); 431 return 1; 432 } 433 434 // Trigger the start event after the service is started. 435 Emit("start", nullptr); 436 437 return 0; 438} 439``` 440 441**4. Call on() to register an event observer.** 442 443```c 444napi_value NetServer::JS_On(napi_env env, napi_callback_info cbinfo) 445{ 446 size_t argc = 2; 447 napi_value argv[2] = {0}; 448 napi_value thisVar = 0; 449 void* data = nullptr; 450 napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data); 451 452 NetServer* netServer = nullptr; 453 // Obtain the NetServer pointer using napi_unwrap. 454 napi_unwrap(env, thisVar, (void**)&netServer); 455 456 NAPI_ASSERT(env, argc >= 2, "requires 2 parameter"); 457 458 // Verify the parameter type. 459 napi_valuetype eventValueType; 460 napi_typeof(env, argv[0], &eventValueType); 461 NAPI_ASSERT(env, eventValueType == napi_string, "type mismatch for parameter 1"); 462 463 napi_valuetype eventHandleType; 464 napi_typeof(env, argv[1], &eventHandleType); 465 NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 2"); 466 467 char type[64] = {0}; 468 size_t typeLen = 0; 469 470 napi_get_value_string_utf8(env, argv[0], type, 63, &typeLen); 471 472 // Register the event handler. 473 netServer->On((const char*)type, argv[1]); 474 475 napi_value result = nullptr; 476 napi_get_undefined(env, &result); 477 return result; 478} 479``` 480 481**JS Sample Code** 482 483```javascript 484import { NetServer } from 'libnetserver.so'; 485 486export default { 487 testNetServer() { 488 var netServer = new NetServer(); 489 netServer.on('start', (event) => {}); 490 netServer.start(1000); // The port number is 1000. After start is executed, invoke the start callback registered. 491 } 492} 493``` 494 495 496 497## Calling Back a JS API in a Non-JS Thread 498 499### Overview 500 501This example describes how to invoke a JS callback in a non-JS thread. For example, a sensor listener is registered for a JS application. The sensor data is reported by an SA. When the SA invokes the client through Inter-Process Communication (IPC), the execution thread is an IPC thread, which is different from the JS thread of the SA. In this case, the JS callback must be thrown to the JS thread to execute. Otherwise, the application will crash. 502 503### Implementation 504 505You can obtain the complete code from **sample/native_module_callback/** in the [OpenHarmony/arkui_napi](https://gitee.com/openharmony/arkui_napi/tree/master) repository. 506 507**1. Register the module.** 508 509Register the **test** API to pass in a parameter. 510 511```c++ 512/*********************************************** 513 * Module export and register 514 ***********************************************/ 515static napi_value CallbackExport(napi_env env, napi_value exports) 516{ 517 static napi_property_descriptor desc[] = { 518 DECLARE_NAPI_FUNCTION("test", JSTest) 519 }; 520 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)); 521 return exports; 522} 523 524// Define the callback. 525static napi_module callbackModule = { 526 .nm_version = 1, 527 .nm_flags = 0, 528 .nm_filename = nullptr, 529 .nm_register_func = CallbackExport, 530 .nm_modname = "callback", 531 .nm_priv = ((void*)0), 532 .reserved = { 0 }, 533}; 534 535// Register the callback. 536extern "C" __attribute__((constructor)) void CallbackTestRegister() 537{ 538 napi_module_register(&callbackModule); 539} 540``` 541 542**2. Obtain the loop in env and throw the task to a JS thread.** 543 544```c++ 545#include <thread> 546 547#include "napi/native_api.h" 548#include "napi/native_node_api.h" 549 550#include "uv.h" 551 552struct CallbackContext { 553 napi_env env = nullptr; 554 napi_ref callbackRef = nullptr; 555 int retData = 0; 556}; 557 558void callbackTest(CallbackContext* context) 559{ 560 uv_loop_s* loop = nullptr; 561 // Save the env when the JS callback is registered. Obtain the loop of the JS thread from env. 562 napi_get_uv_event_loop(context->env, &loop); 563 564 // Create uv_work_t to transfer private data (int type 1 in this example). Note that memory must be released after the callback is complete. The logic for generating the returned data is omitted here. 565 uv_work_t* work = new uv_work_t; 566 context->retData = 1; 567 work->data = (void*)context; 568 569 // Call the libuv API to throw the JS task to the loop for execution. 570 uv_queue_work( 571 loop, 572 work, 573 // This callback is executed in another common thread to process tasks asynchronously. After the callback is complete, execute the next callback. In this scenario, this callback does not need to execute any task. 574 [](uv_work_t* work) {}, 575 // This callback is executed in the JS thread bound to env. 576 [](uv_work_t* work, int status) { 577 CallbackContext* context = (CallbackContext*)work->data; 578 napi_handle_scope scope = nullptr; 579 // Open the handle scope to manage the lifecycle of napi_value. Otherwise, memory leakage occurs. 580 napi_open_handle_scope(context->env, &scope); 581 if (scope == nullptr) { 582 return; 583 } 584 585 // Call the NAPIs. 586 napi_value callback = nullptr; 587 napi_get_reference_value(context->env, context->callbackRef, &callback); 588 napi_value retArg; 589 napi_create_int32(context->env, context->retData, &retArg); 590 napi_value ret; 591 napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret); 592 napi_delete_reference(context->env, context->callbackRef); 593 594 // Close the handle scope to release napi_value. 595 napi_close_handle_scope(context->env, scope); 596 597 // Release the work pointer. 598 if (work != nullptr) { 599 delete work; 600 } 601 602 delete context; 603 } 604 ); 605} 606 607static napi_value JSTest(napi_env env, napi_callback_info info) 608{ 609 size_t argc = 1; 610 napi_value argv[1] = { 0 }; 611 napi_value thisVar = nullptr; 612 void* data = nullptr; 613 napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); 614 615 // Obtain the first input parameter, that is, the callback to be invoked subsequently. 616 napi_valuetype valueType = napi_undefined; 617 napi_typeof(env, argv[0], &valueType); 618 if (valueType != napi_function) { 619 return nullptr; 620 } 621 // Save the env and callback for subsequent transfer. 622 auto asyncContext = new CallbackContext(); 623 asyncContext->env = env; 624 napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef); 625 // Simulate the logic for throwing a task to a non-JS thread. 626 std::thread testThread(callbackTest, asyncContext); 627 testThread.detach(); 628 629 return nullptr; 630} 631``` 632 633**JS Sample Code** 634 635```js 636import callback from 'libcallback.so'; 637 638export default { 639 testcallback() { 640 callback.test((data) => { 641 console.error('test result = ' + data) 642 }) 643 } 644} 645``` 646