1# Node-API Development Specifications 2 3## Obtaining Arguments Passed by JS 4 5**[Rule]** When **argv** in **napi_get_cb_info** is not **nullptr**, the length of **argv** must be greater than or equal to **argc**. 6 7If **argv** is not **nullptr**, the arguments actually passed by JS will be copied to **argv** in **napi_get_cb_info** based on the value of **argc**. If there are more arguments than the provided count, only the requested number of arguments are copied. If there are fewer arguments provided than the claimed, the rest of **argv** is filled with values that represent **undefined**. 8 9**Example (incorrect)** 10 11```cpp 12static napi_value IncorrectDemo1(napi_env env, napi_callback_info info) { 13 // argc is not correctly initialized and is set to a random value. If the length of argv is less than the number of arguments specified by argc, data overwriting occurs. 14 size_t argc; 15 napi_value argv[10] = {nullptr}; 16 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 17 return nullptr; 18} 19 20static napi_value IncorrectDemo2(napi_env env, napi_callback_info info) { 21 // The number of arguments specified by argc is greater than the length of argv. As a result, data overwriting occurs when napi_get_cb_info writes argv. 22 size_t argc = 5; 23 napi_value argv[3] = {nullptr}; 24 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 25 return nullptr; 26} 27``` 28 29**Example (correct)** 30 31```cpp 32static napi_value GetArgvDemo1(napi_env env, napi_callback_info info) { 33 size_t argc = 0; 34 // Pass in nullptr to argv to obtain the actual number of arguments passed by JS. 35 napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr); 36 // If 0 is passed by JS, the subsequent logic is not executed. 37 if (argc == 0) { 38 return nullptr; 39 } 40 // Create an array to obtain the arguments passed by JS. 41 napi_value* argv = new napi_value[argc]; 42 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 43 // Service code. 44 // ... 45 // argv is an object created by new and must be manually released when it is not required. 46 delete argv; 47 return nullptr; 48} 49 50static napi_value GetArgvDemo2(napi_env env, napi_callback_info info) { 51 size_t argc = 2; 52 napi_value argv[2] = {nullptr}; 53 // napi_get_cb_info writes the arguments (of the quantity specified by argc) passed by JS or undefined to argv. 54 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 55 // Service code. 56 // ... 57 return nullptr; 58} 59``` 60 61## Lifecycle Management 62 63**[Rule]** Properly use **napi_open_handle_scope** and **napi_close_handle_scope** to minimize the lifecycle of **napi_value** and avoid memory leaks. 64 65Each **napi_value** belongs to a specific **HandleScope**, which is opened and closed by **napi_open_handle_scope** and **napi_close_handle_scope**, respectively. After a **HandleScope** is closed, its **napi_value** is automatically released. 66 67**Example (correct)** 68 69```cpp 70// When Node-API is frequently called to create JS objects in the for loop, use handle_scope to release resources in a timely manner when they are no longer used. 71// In the following example, the lifecycle of the local variable res ends at the end of each loop. To prevent memory leaks, scope is used to release the JS object in a timely manner. 72for (int i = 0; i < 100000; i++) { 73 napi_handle_scope scope = nullptr; 74 napi_open_handle_scope(env, &scope); 75 if (scope == nullptr) { 76 return; 77 } 78 napi_value res; 79 napi_create_object(env, &res); 80 napi_close_handle_scope(env, scope); 81} 82``` 83 84## Context Sensitive 85 86**[Rule]** Do not use Node-API to access JS objects across engine instances. 87 88An engine instance is an independent running environment. Operations such as creating and accessing a JS object must be performed in the same engine instance. If an object is operated in different engine instances, the application may crash. An engine instance is represented as a value of **napi_env** in APIs. 89 90**Example (incorrect)** 91 92```cpp 93// Create a string object with value of "bar" in env1. 94napi_create_string_utf8(env1, "bar", NAPI_AUTO_LENGTH, &string); 95// Create an object in env2 and set the string object to this object. 96napi_status status = napi_create_object(env2, &object); 97if (status != napi_ok) { 98 napi_throw_error(env, ...); 99 return; 100} 101 102status = napi_set_named_property(env2, object, "foo", string); 103if (status != napi_ok) { 104 napi_throw_error(env, ...); 105 return; 106} 107``` 108 109JS objects belong to a specific **napi_env**. Therefore, you cannot set an object of env1 to an object of env2. If the object of env1 is accessed in env2, the application may crash. 110 111## Exception Handling 112 113**[Suggestion]** Any exception occurred in a Node-API call should be handled in a timely manner. Otherwise, unexpected behavior may occur. 114 115**Example (correct)** 116 117```cpp 118// 1. Create an object. 119napi_status status = napi_create_object(env, &object); 120if (status != napi_ok) { 121 napi_throw_error(env, ...); 122 return; 123} 124// 2. Create a property. 125status = napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, &string); 126if (status != napi_ok) { 127 napi_throw_error(env, ...); 128 return; 129} 130// 3. Set the result of step 2 to the value of the object property foo. 131status = napi_set_named_property(env, object, "foo", string); 132if (status != napi_ok) { 133 napi_throw_error(env, ...); 134 return; 135} 136``` 137 138In this example, if an exception occurs in step 1 or step 2, step 3 will not be performed. Step 3 will be performed only when napi_ok is returned in steps 1 and 2. 139 140## Asynchronous Tasks 141 142**[Rule]** When the **uv_queue_work** method is called to throw a work to a JS thread for execution, use **napi_handle_scope** to manage the lifecycle of **napi_value** created by the JS callback. 143 144The Node-API framework will not be used when the **uv_queue_work** method is called. In this case, you must use **napi_handle_scope** to manage the lifecycle of **napi_value**. 145 146> **NOTE** 147> 148> This rule focuses on the **napi_value** lifecycle. If you only want to throw tasks to the JS thread, you are advised not to use **uv_queue_work**. To throw tasks, use [napi_threadsafe_function](./use-napi-thread-safety.md) APIs. 149 150**Example (correct)** 151 152```cpp 153void callbackTest(CallbackContext* context) 154{ 155 uv_loop_s* loop = nullptr; 156 napi_get_uv_event_loop(context->env, &loop); 157 uv_work_t* work = new uv_work_t; 158 context->retData = 1; 159 work->data = (void*)context; 160 uv_queue_work( 161 loop, work, 162 // Note that uv_queue_work creates a thread and executes the callback. If you only want to throw tasks to the JS thread, you are advised not to use uv_queue_work to avoid creation of redundant threads. 163 [](uv_work_t* work) { 164 // Execute service logic. 165 }, 166 // The callback is executed on the JS thread where the loop is located. 167 [](uv_work_t* work, int status) { 168 CallbackContext* context = (CallbackContext*)work->data; 169 napi_handle_scope scope = nullptr; 170 napi_open_handle_scope(context->env, &scope); 171 if (scope == nullptr) { 172 if (work != nullptr) { 173 delete work; 174 } 175 return; 176 } 177 napi_value callback = nullptr; 178 napi_get_reference_value(context->env, context->callbackRef, &callback); 179 napi_value retArg; 180 napi_create_int32(context->env, context->retData, &retArg); 181 napi_value ret; 182 napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret); 183 napi_delete_reference(context->env, context->callbackRef); 184 napi_close_handle_scope(context->env, scope); 185 if (work != nullptr) { 186 delete work; 187 } 188 delete context; 189 } 190 ); 191} 192``` 193 194## Object Wrapping 195 196**[Rule]** If the value of the last parameter **result** is not **nullptr** in **napi_wrap()**, use **napi_remove_wrap()** at a proper time to delete the created **napi_ref**. 197 198The **napi_wrap** interface is defined as follows: 199 200```cpp 201napi_wrap(napi_env env, napi_value js_object, void* native_object, napi_finalize finalize_cb, void* finalize_hint, napi_ref* result) 202``` 203 204When the last parameter **result** is not null, the Node-API framework creates an **napi_ref** object pointing to **js_object**. You need to manage the lifecycle of **js_object**. Specifically, use **napi_remove_wrap** to delete **napi_ref** at a proper time so that the garbage collector (GC) can release **js_object** and trigger the destructor **finalize_cb** bound to the C++ object **native_object**. 205 206Generally, you can directly pass in **nullptr** for the last parameter **result**. 207 208**Example (correct)** 209 210```cpp 211// Case 1: Pass in nullptr via the last parameter in napi_wrap. In this case, the created napi_ref is a weak reference, which is managed by the system and does not need manual release. 212napi_wrap(env, jsobject, nativeObject, cb, nullptr, nullptr); 213 214// Case 2: The last parameter in napi_wrap is not nullptr. In this case, the returned napi_ref is a strong reference and needs to be manually released. Otherwise, a memory leak may occur. 215napi_ref result; 216napi_wrap(env, jsobject, nativeObject, cb, nullptr, &result); 217// When js_object and result are no longer used, call napi_remove_wrap to release result. 218void* nativeObjectResult = nullptr; 219napi_remove_wrap(env, jsobject, &nativeObjectResult); 220``` 221 222## Arrays for High Performance 223 224**[Suggestion]** Use ArrayBuffer instead of JSArray to store value-type data for higher performance. 225 226JSArray is used as a container to store data and supports almost all JS data types. 227 228When **napi_set_element** is used to store value-type data (such as int32) in JSArray, interaction with the runtime is involved, which causes unnecessary overhead. 229 230The operations on ArrayBuffer are performed in the buffer, which delivers higher performance than using **napi_set_element** to operate JSArray. 231 232Therefore, you are advised to use the **ArrayBuffer** object created by **napi_create_arraybuffer** in this scenario. 233 234**Example:** 235 236```cpp 237// In the following code, JSArray is used to store only int32 data. 238// Since JSArray is a JS object, only Node-API methods can be used to operate it, which compromises the performance. 239static napi_value ArrayDemo(napi_env env, napi_callback_info info) 240{ 241 constexpr size_t arrSize = 1000; 242 napi_value jsArr = nullptr; 243 napi_create_array(env, &jsArr); 244 for (int i = 0; i < arrSize; i++) { 245 napi_value arrValue = nullptr; 246 napi_create_int32(env, i, &arrValue); 247 // Using Node-API methods to read and write JSArray affects the performance. 248 napi_set_element(env, jsArr, i, arrValue); 249 } 250 return jsArr; 251} 252 253// To improve the performance, modify the code as follows: 254// Use ArrayBuffer to hold int32 data. 255// In this case, C/C++ methods can be used to directly add or modify data in the buffer. 256static napi_value ArrayBufferDemo(napi_env env, napi_callback_info info) 257{ 258 constexpr size_t arrSize = 1000; 259 napi_value arrBuffer = nullptr; 260 void* data = nullptr; 261 262 napi_create_arraybuffer(env, arrSize * sizeof(int32_t), &data, &arrBuffer); 263 // data is a null pointer. Cancel the write of data. 264 if (data == nullptr) { 265 return arrBuffer; 266 } 267 int32_t* i32Buffer = reinterpret_cast<int32_t*>(data); 268 for (int i = 0; i < arrSize; i++) { 269 // Using arrayBuffer allows data to be directly modified in the buffer, which eliminates the interaction with the runtime. 270 // The performance is equivalent to that of operating native C/C++ objects. 271 i32Buffer[i] = i; 272 } 273 274 return arrBuffer; 275} 276``` 277 278**napi_create_arraybuffer** is equivalent to **new ArrayBuffer(size)** in JS. The object generated cannot be directly read in TS/JS. It can be read or written only after being encapsulated into a TyppedArray or DataView object. 279 280**Benchmark performance test result**: 281 282> **NOTE** 283> 284> The following data is the accumulated data written in thousands of cycles. To better reflect the difference, the core frequency of the device has been limited. 285 286| Container Type | Benchmark Data (us)| 287| ----------- | ------------------- | 288| JSArray | 1566.174 | 289| ArrayBuffer | 3.609 | 290 291## Data Conversion 292 293**[Suggestion]** Minimize the number of data conversions and avoid unnecessary replication. 294 295- Frequent data conversion affects performance. You are advised to use batch data processing or optimize the data structs to improve performance. 296- During data conversion, use Node-API to access the original data instead of creating a copy. 297- For the data that may be used in multiple conversions, store it in a buffer to avoid repeated data conversions. In this way, unnecessary calculations can be reduced, leading to better performance. 298 299## Module Registration and Naming 300 301**[Rule]** 302Add "static" to the function corresponding to **nm_register_func** to prevent conflicts with symbols in other .so files. 303 304The module registration entry, that is, the name of the function decorated by **__attribute__((constructor))** must be different from that of other modules. 305 306The **.nm_modname** field must completely match the module name and is case sensitive. 307 308**Example (incorrect)** 309In the following example, the module name is **nativerender**. 310 311```cpp 312EXTERN_C_START 313napi_value Init(napi_env env, napi_value exports) 314{ 315 // ... 316 return exports; 317} 318EXTERN_C_END 319 320static napi_module nativeModule = { 321 .nm_version = 1, 322 .nm_flags = 0, 323 .nm_filename = nullptr, 324 // static is not added to the function corresponding to nm_register_func. 325 .nm_register_func = Init, 326 // The .nm_modname value does not match the module name. As a result, the module fails to be loaded in the multi-thread scenario. 327 .nm_modname = "entry", 328 .nm_priv = nullptr, 329 .reserved = { 0 }, 330}; 331 332// The name of the module registration entry function is RegisterModule, which is easy to be duplicate with that of other modules. 333extern "C" __attribute__((constructor)) void RegisterModule() 334{ 335 napi_module_register(&nativeModule); 336} 337``` 338 339**Example (correct)** 340In the following example, the module name is **nativerender**. 341 342```cpp 343EXTERN_C_START 344static napi_value Init(napi_env env, napi_value exports) 345{ 346 // ... 347 return exports; 348} 349EXTERN_C_END 350 351static napi_module nativeModule = { 352 .nm_version = 1, 353 .nm_flags = 0, 354 .nm_filename = nullptr, 355 .nm_register_func = Init, 356 .nm_modname = "nativerender", 357 .nm_priv = nullptr, 358 .reserved = { 0 }, 359}; 360 361extern "C" __attribute__((constructor)) void RegisterNativeRenderModule() 362{ 363 napi_module_register(&nativeModule); 364} 365``` 366 367## Using JS Objects Created by napi_create_external APIs 368 369**[Rule]** The JS object created by **napi_create_external** APIs can be passed and used only in the calling thread. If it is passed across threads (for example, using **post_message** of the **worker** interface), the application may crash. If a JS object bound with a native object needs to be passed across threads, use **napi_coerce_to_native_binding_object** to bind the two objects. 370 371**Example (incorrect)** 372 373```cpp 374static void MyFinalizeCB(napi_env env, void *finalize_data, void *finalize_hint) { return; }; 375 376static napi_value CreateMyExternal(napi_env env, napi_callback_info info) { 377 napi_value result = nullptr; 378 napi_create_external(env, nullptr, MyFinalizeCB, nullptr, &result); 379 return result; 380} 381 382// The code for module registration is omitted here. You may need to register the CreateMyExternal method. 383``` 384 385```ts 386// index.d.ts 387export const createMyExternal: () => Object; 388 389// Application code. 390import testNapi from 'libentry.so'; 391import worker from '@ohos.worker'; 392 393const mWorker = new worker.ThreadWorker('../workers/Worker'); 394 395{ 396 const mExternalObj = testNapi.createMyExternal(); 397 398 mWorker.postMessage(mExternalObj); 399 400} 401 402// Close the worker thread. 403// The application may crash in this step or when the engine performs GC. 404mWorker.terminate(); 405// The implementation of worker is the default template, which is omitted here. 406``` 407 408## Avoiding Releasing the Obtained Buffer Repeatedly 409 410**[Rule]** The parameter **data** in certain APIs, such as **napi_get_arraybuffer_info**, cannot be released manually. Its lifecycle is managed by the engine. 411 412The following uses **napi_get_arraybuffer_info** as an example to describe the interface definition: 413 414```cpp 415napi_get_arraybuffer_info(napi_env env, napi_value arraybuffer, void** data, size_t* byte_length) 416``` 417 418The parameter **data** specifies the buffer header pointer to **ArrayBuffer**. This buffer can be read and written in the given range but cannot be released. The buffer memory is managed by the ArrayBuffer Allocator in the engine and is released with the lifecycle of the JS object **ArrayBuffer**. 419 420**Example (incorrect)** 421 422```cpp 423void* arrayBufferPtr = nullptr; 424napi_value arrayBuffer = nullptr; 425size_t createBufferSize = ARRAY_BUFFER_SIZE; 426napi_status verification = napi_create_arraybuffer(env, createBufferSize, &arrayBufferPtr, &arrayBuffer); 427size_t arrayBufferSize; 428napi_status result = napi_get_arraybuffer_info(env, arrayBuffer, &arrayBufferPtr, &arrayBufferSize); 429delete arrayBufferPtr; // This operation is not allowed and may cause a double free of the buffer. The lifecycle of the created arrayBufferPtr is managed by the engine and cannot be manually deleted. 430``` 431 432|Applicable APIs| 433|----------------------------------| 434| napi_create_arraybuffer | 435| napi_create_sendable_arraybuffer | 436| napi_get_arraybuffer_info | 437| napi_create_buffer | 438| napi_get_buffer_info | 439| napi_get_typedarray_info | 440| napi_get_dataview_info | 441 442## Others 443 444**[Suggestion]** Properly use **napi_object_freeze** and **napi_object_seal**. 445 446**napi_object_freeze** is equivalent to **Object.freeze**. After an object is frozen, all its properties are immutable. **napi_object_seal** is equivalent to **Object.seal**. After an object is sealed, no properties can be added or deleted, but the existing property values are mutable. 447 448If the semantics are violated in strict mode (default), an error will be thrown. 449