1# Secure and Efficient N-API Development
2
3## Introduction
4
5N-API, short for Node.js Addon Programming Interface, is a set of C++ APIs provided by Node.js. Shipped with the capabilities of the [V8 engine](https://v8.dev/docs), it is used to build Node.js native addons. By drawing on N-API, you can write high-performance Node.js modules in C++ while maintaining compatibility with Node.js.
6
7N-API provided in [ArkCompiler ArkTS runtime](https://gitee.com/openharmony/arkcompiler_ets_runtime), shipped with the capabilities of the Ark engine, provides the same basic functionality as described in the [Node.js documentation](https://nodejs.org/api/n-api.html).
8
9This document is intended to provide guidance on secure and efficient N-API development. Designed with carefully curated use cases, it is structured into four parts: object lifetime management, cross-language invocation overhead, asynchronous operation, and thread security.
10
11## Object Lifetime Management
12
13As N-API calls are made, handles to objects in the VM heap may be returned as **napi_value**s. These handles control the lifetime of the objects. Object handles are associated with a scope. The default lifespan of the scope is tied to the lifespan of the native method. In some real-world cases, however, an object may require a scope with a shorter or longer lifespan than that of the native method. This section describes how to use N-API functions to change the object lifespan.
14
15### Shortening Object Lifespan
16
17You can use **napi_open_handle_scope** and **napi_close_handle_scope** to manage the lifetime of **napi_value**, so as to minimize the object lifespan and avoid memory leak.
18
19For example, consider a method that has a **for** loop, which iterates through the elements in a large array:
20```cpp
21for (int i = 0; i < 1000000; i++) {
22 napi_value result;
23 napi_status status = napi_get_element(env, object, i, &result);
24 if (status != napi_ok) {
25  break;
26 }
27 // do something with element
28}
29```
30
31In this case, a large number of handles may be created, consuming substantial resources. To handle this case, N-API provides the function to establish a new scope to which newly created handles will be associated. Once those handles are no longer needed, the scope can be closed and any handles associated with the scope are invalidated.
32
33* The methods available to open and close scopes are **napi_open_handle_scope** and **napi_close_handle_scope**, respectively.
34* N-API only supports a single nested hierarchy of scopes. There is only one active scope at any time, and all new handles will be associated with that scope while it is active.
35* Scopes must be closed in the reverse order from which they are opened. In addition, all scopes created within a native method must be closed before returning from that method.
36
37The following is an optimization based on the earlier example. In this example, at most a single handle is valid throughout the execution of the loop.
38```cpp
39// When N-API is frequently called to create JS objects in the for loop, add handle_scope to release in a timely manner resources that are no longer used.
40// In the following code, the lifespan of the local variable res ends at the end of each loop. Therefore, a scope is added to release the JS object in time to prevent memory leak.
41for (int i = 0; i < 1000000; i++) {
42    napi_handle_scope scope;
43    napi_status status = napi_open_handle_scope(env, &scope);
44    if (status != napi_ok) {
45        break;
46    }
47    napi_value result;
48    status = napi_get_element(env, object, i, &result);
49    if (status != napi_ok) {
50        break;
51    }
52    // do something with element
53    status = napi_close_handle_scope(env, scope);
54    if (status != napi_ok) {
55        break;
56    }
57}
58```
59There are cases where a handle from a scope needs to outlive the scope, for example, in nested loop scenarios. For this purpose, you can use **napi_open_escapable_handle_scope** and **napi_close_escapable_handle_scope** to open and close an escapable scope, where the lifespan of the defined handle is the same as that of the outer scope.
60
61### Extending Object Lifespan
62
63You can extend the lifespan of an **napi_value** object, by creating an **napi_ref** reference to a **napi_value** object. All **napi_ref** references, created by calling **napi_create_reference**, must be deleted manually by calling **napi_delete_reference**. Failure to delete a reference may results in memory leak.
64
65#### Use Case 1: Saving napi_value
66
67Use **napi_define_class** to create a constructor and save it. You can use the saved constructor to call **napi_new_instance** to create an instance. However, if the constructor is saved as a **napi_value**, it will be destructed once it exceeds the scope of the native method. In this case, continuing to use the constructor will result in a wild pointer. To avoid this issue, it is recommended that you:
68* Save the constructor as an **napi_ref** reference.
69* Manage the lifespan of the constructor object on your own so that it is not restricted by the scope of the native method.
70```cpp
71// 1. Save the constructor as an napi_ref reference.
72static napi_value TestDefineClass(napi_env env,
73                                  napi_callback_info info) {
74  napi_status status;
75  napi_value result, return_value;
76
77  napi_property_descriptor property_descriptor = {
78    "TestDefineClass",
79    NULL,
80    TestDefineClass,
81    NULL,
82    NULL,
83    NULL,
84    napi_enumerable | napi_static,
85    NULL};
86
87  NODE_API_CALL(env, napi_create_object(env, &return_value));
88
89  status = napi_define_class(NULL,
90                             "TrackedFunction",
91                             NAPI_AUTO_LENGTH,
92                             TestDefineClass,
93                             NULL,
94                             1,
95                             &property_descriptor,
96                             &result);
97  SaveConstructor(env, result);
98  ...
99}
100```
101```cpp
102// 2. Manage the lifespan of the constructor object on your own.
103napi_status SaveConstructor(napi_env env, napi_value constructor) {
104    return napi_create_reference(env, constructor, 1, &g_constructor);
105};
106
107napi_status GetConstructor(napi_env env) {
108    napi_value constructor;
109    return napi_get_reference_value(env, g_constructor, &constructor);
110};
111```
112
113#### Use Case 2: napi_wrap
114
115You can use **napi_wrap** to wrap a native instance in a JavaScript object. When the JavaScript object is garbage-collected, use a callback to free the native instance. In effect, the **napi_wrap** API creates an **napi_ref** reference. You can designate the system to manage the created **napi_ref** reference. Alternatively, you can release the reference manually.
116```cpp
117// Usage 1: napi_wrap does not need to receive the created napi_ref reference. The last parameter is nullptr. The created napi_ref reference is managed by the system and does not need to be manually released.
118napi_wrap(env, jsobject, nativeObject, cb, nullptr, nullptr);
119
120// Usage 2: napi_wrap needs to receive the created napi_ref reference. The last parameter is not nullptr. The created napi_ref reference needs to be manually released. Otherwise, memory leak occurs.
121napi_ref result;
122napi_wrap(env, jsobject, nativeObject, cb, nullptr, &result);
123// When the JavaScript object and result are no longer used, call napi_remove_wrap to release the result in a timely manner.
124napi_value result1;
125napi_remove_wrap(env, jsobject, result1)
126```
127
128## Cross-Language Invocation Overhead
129
130### API Call
131
132Cross-language invocation is the process where code written in different programming languages calls and interacts with each other in an application. For example, ArkTS invoking C++ is one of the cross-language invocation modes. Using N-API to call functions causes certain overhead, because context switching, parameter transfer, function invoking, and return value processing are required. Currently, there are three scenarios where C++ code is called from ArkTS through N-API: call C++ APIs, listen for C++ APIs , and receive C++ callbacks. Frequent cross-language API calls may detract from service performance. Therefore, properly design the API call frequency.
133
134### Value Conversion
135
136When using N-API to convert data between ArkTS and C++, take note of the following:
137* Reduce the number of data conversion times: Frequent data conversion may cause performance deterioration. You can process data in batches or use a more efficient data structure to optimize performance.
138* Avoid unnecessary data replication: During data conversion, use N-API to directly access the original data instead of creating a new data copy.
139* Use cache: If some data is used in multiple conversions, you can store it in the cache to avoid repeated data conversions. In this way, unnecessary calculations can be reduced, leading to better performance.
140
141## Asynchronous Operations
142I/O- and CPU-intensive tasks must be processed asynchronously. Otherwise, the main thread will be blocked. N-API provides the asynchronous capability, allowing an application to continue executing other tasks instead of being blocked when executing a time-consuming task. When an asynchronous operation is complete, the application receives a notification and can process the operation result.
143
144### Example of Asynchronous Processing
145
146You can refer to the following example to implement time-consuming tasks in asynchronous mode. The logic includes the following steps:
147* Call **napi_create_promise** to create a promise. When a promise is created, a "deferred" object is created and returned alongside the promise.
148* Execute the time-consuming task and pass the execution result to the promise.
149* Call **napi_resolve_deferred** or **napi_reject_deferred** to resolve or reject the created promise and release the deferred object.
150
151```cpp
152// Transfer data between executeCB and completeCB.
153struct AddonData {
154    napi_async_work asyncWork = nullptr;
155    napi_deferred deferred = nullptr;
156    napi_ref callback = nullptr;
157
158    double args[2] = {0};
159    double result = 0;
160};
161
162// 2. Execute the time-consuming task and pass the execution result to the promise.
163static void addExecuteCB(napi_env env, void *data) {
164    AddonData *addonData = (AddonData *)data;
165    addonData->result = addonData->args[0] + addonData->args[1];
166};
167
168// 3. Call napi_resolve_deferred or napi_reject_deferred to resolve or reject the created promise and release the deferred object.
169static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
170    AddonData *addonData = (AddonData *)data;
171    napi_value result = nullptr;
172    napi_create_double(env, addonData->result, &result);
173    napi_resolve_deferred(env, addonData->deferred, result);
174
175    if (addonData->callback != nullptr) {
176        napi_delete_reference(env, addonData->callback);
177    }
178
179    // Delete the asynchronous work.
180    napi_delete_async_work(env, addonData->asyncWork);
181    delete addonData;
182    addonData = nullptr;
183};
184
185// 1. Call napi_create_promise to create a promise. When a promise is created,
186// a "deferred" object is created and returned alongside the promise.
187static napi_value addPromise(napi_env env, napi_callback_info info) {
188    size_t argc = 2;
189    napi_value args[2];
190    napi_value thisArg = nullptr;
191    napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr);
192
193    napi_valuetype valuetype0;
194    napi_typeof(env, args[0], &valuetype0);
195    napi_valuetype valuetype1;
196    napi_typeof(env, args[1], &valuetype1);
197    if (valuetype0 != napi_number || valuetype1 != napi_number) {
198        napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
199        return NULL;
200    }
201
202    napi_value promise = nullptr;
203    napi_deferred deferred = nullptr;
204    napi_create_promise(env, &deferred, &promise);
205
206    // Asynchronous work context user data, which is transferred between execute and complete callbacks of the asynchronous work.
207    auto addonData = new AddonData{
208        .asyncWork = nullptr,
209        .deferred = deferred,
210    };
211
212    napi_get_value_double(env, args[0], &addonData->args[0]);
213    napi_get_value_double(env, args[1], &addonData->args[1]);
214
215    // Create an async work. When the creation is successful, the handle to the async work is returned through the last parameter (addonData->asyncWork).
216    napi_value resourceName = nullptr;
217    napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
218    napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
219                           &addonData->asyncWork);
220
221    // Add the newly created async work to the queue for the underlying layer to schedule and execute.
222    napi_queue_async_work(env, addonData->asyncWork);
223
224    return promise;
225}
226```
227
228After the asynchronous operation is complete, the callback is invoked and the result is passed to the **Promise** object. In JavaScript, you can use the **then()** method of the **Promise** object to process the result of an asynchronous operation.
229
230```js
231import hilog from '@ohos.hilog';
232import testNapi from 'libentry.so'
233
234@Entry
235@Component
236struct TestAdd {
237  build() {
238    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
239      Text("hello world")
240        .onClick(() => {
241          let num1 = 2;
242          let num2 = 3;
243          testNapi.addPromise(num1, num2).then((result) => {
244            hilog.info(0x0000, 'testTag', '%{public}d', result);
245          })
246        })
247    }
248    .width('100%')
249    .height('100%')
250  }
251}
252```
253
254### Prioritizing Asynchronous Tasks
255
256For applications developed using [Function Flow](https://gitee.com/openharmony/resourceschedule_ffrt/blob/master/docs/user_guide.md)), a task-based and data-driven concurrent programming model, you can schedule tasks with the help of the Function Flow Runtime (FFRT). Ark ArkTS runtime provides an extension API, which you can call to invoke FFRT with the QoS information of tasks passed in. The tasks are then scheduled based on their QoS level and system resource usage for a balance between power consumption and performance.
257
258* API example: napi_status napi_queue_async_work_with_qos(napi_env env, napi_async_work work, napi_qos_t qos) ()
259  * **env**: environment for invoking the API.
260  * **napi_async_work**: asynchronous task.
261  * **napi_qos_t**: QoS level.
262
263* QoS levels:
264```cpp
265typedef enum {
266    napi_qos_background = 0,
267    napi_qos_utility = 1,
268    napi_qos_default = 2,
269    napi_qos_user_initiated = 3,
270} napi_qos_t;
271```
272
273* The N-API layer encapsulates an external API to connect to the **uv_queue_work_with_qos(uv_loop_t* loop, uv_work_t* req, uv_work_cb work_cb, uv_after_work_cb after_work_cb, uv_qos_t qos)** function at the libuv layer.
274
275* Compared with the existing API **napi_queue_async_work**, this API adds the QoS level to specify the priority for task scheduling. Example:
276```cpp
277static void PromiseOnExec(napi_env env, void *data) {
278    OH_LOG_INFO(LOG_APP, "PromiseOnExec");
279}
280
281static void PromiseOnComplete(napi_env env, napi_status status, void *data) {
282    int number = *((int *)data);
283    OH_LOG_INFO(LOG_APP, "PromiseOnComplete number = %{public}d", number);
284}
285
286static napi_value Test(napi_env env, napi_callback_info info) {
287    napi_value resourceName = nullptr;
288    napi_create_string_utf8(env, "TestExample", NAPI_AUTO_LENGTH, &resourceName);
289    napi_async_work async_work;
290    int *data = new int(10);
291    napi_create_async_work(env, nullptr, resourceName, PromiseOnExec, PromiseOnComplete, data, &async_work);
292    napi_queue_async_work_with_qos(env, async_work, napi_qos_default);
293    return nullptr;
294}
295```
296
297## Thread Security
298
299If an application needs to perform a large number of computing or I/O operations, the concurrency mechanism is handy, in that it can make full use of the multi-core CPU to improve the processing efficiency of the application. For example, applications specialized in image processing, video coding, and data analysis may use a concurrency mechanism to improve processing speeds.
300
301Although N-API itself does not support concurrent operations with multithreading, it does allow for some data interactions in a multi-thread environment, where thread security would be a concern. In a multi-thread environment, you can use **napi_create_threadsafe_function** to create a thread-safe function and then call the function in any thread.
302
303*Application scenario*: When there are other threads on the native side and JavaScript functions need to be called based on the completion results of these threads, these threads must communicate with the main thread on the native side so that JavaScript functions can be called in the main thread. Thread-safe functions provide a simplified way to avoid inter-thread communication and to return to the main thread to call JavaScript functions.
304
305### How to Use
306
307#### Passing Callback from the ArkTS Side
308```JS
309struct Index {
310  @State message: string = 'Hello World'
311
312  build() {
313    Row() {
314      Column() {
315        Text(this.message)
316          .fontSize(50)
317          .fontWeight(FontWeight.Bold)
318          .onClick(() => {
319            testNapi.threadSafeTest((value) => {
320              hilog.info(0x0000, 'testTag', 'js callback value = ' + value);
321            })
322          })
323      }
324      .width('100%')
325    }
326    .height('100%')
327  }
328}
329```
330
331#### Creating Thread-Safe Function in Main Thread on Native Side
332```cpp
333static void CallJs(napi_env env, napi_value js_cb, void *context, void *data) {
334
335    std::thread::id this_id = std::this_thread::get_id();
336    OH_LOG_INFO(LOG_APP, "thread CallJs %{public}d.\n", this_id);
337    napi_status status;
338
339    status = napi_get_reference_value(env, cbObj, &js_cb);
340
341    napi_valuetype valueType = napi_undefined;
342    napi_typeof(env, js_cb, &valueType);
343    OH_LOG_INFO(LOG_APP, "CallJs js_cb is napi_function: %{public}d", valueType == napi_function);
344
345    OH_LOG_INFO(LOG_APP, "CallJs 0");
346    if (env != NULL) {
347        napi_value undefined, js_the_prime;
348        status = napi_create_int32(env, 666, &js_the_prime);
349        OH_LOG_INFO(LOG_APP, "CallJs 1: %{public}d", status == napi_ok);
350        status = napi_get_undefined(env, &undefined);
351        OH_LOG_INFO(LOG_APP, "CallJs 2: %{public}d", status == napi_ok);
352
353        napi_value ret;
354
355        status = napi_call_function(env, undefined, js_cb, 1, &js_the_prime, &ret);
356        OH_LOG_INFO(LOG_APP, "CallJs 3: %{public}d", status == napi_ok);
357    }
358}
359
360napi_threadsafe_function tsfn;
361
362static napi_value ThreadSafeTest(napi_env env, napi_callback_info info) {
363    size_t argc = 1;
364    napi_value js_cb, work_name;
365    napi_status status;
366
367    status = napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL);
368    OH_LOG_INFO(LOG_APP, "ThreadSafeTest 0: %{public}d", status == napi_ok);
369
370    status = napi_create_reference(env, js_cb, 1, &cbObj);
371    OH_LOG_INFO(LOG_APP, "napi_create_reference of js_cb to cbObj: %{public}d", status == napi_ok);
372
373    status =
374        napi_create_string_utf8(env, "Node-API Thread-safe Call from Async Work Item", NAPI_AUTO_LENGTH, &work_name);
375    OH_LOG_INFO(LOG_APP, "ThreadSafeTest 1: %{public}d", status == napi_ok);
376
377    std::thread::id this_id = std::this_thread::get_id();
378    OH_LOG_INFO(LOG_APP, "thread ThreadSafeTest %{public}d.\n", this_id);
379
380    napi_valuetype valueType = napi_undefined;
381    napi_typeof(env, js_cb, &valueType);
382    OH_LOG_INFO(LOG_APP, "ThreadSafeTest js_cb is napi_function: %{public}d", valueType == napi_function);
383
384    status = napi_create_threadsafe_function(env, js_cb, NULL, work_name, 0, 1, NULL, NULL, NULL, CallJs, &tsfn);
385    OH_LOG_INFO(LOG_APP, "ThreadSafeTest 2: %{public}d", status == napi_ok);
386}
387```
388
389#### Calling Thread-Safe Functions in Other Threads
390```cpp
391std::thread t([]() {
392    std::thread::id this_id = std::this_thread::get_id();
393    OH_LOG_INFO(LOG_APP, "thread0 %{public}d.\n", this_id);
394    napi_status status;
395    status = napi_acquire_threadsafe_function(tsfn);
396    OH_LOG_INFO(LOG_APP, "thread1 : %{public}d", status == napi_ok);
397    status = napi_call_threadsafe_function(tsfn, NULL, napi_tsfn_blocking);
398    OH_LOG_INFO(LOG_APP, "thread2 : %{public}d", status == napi_ok);
399});
400t.detach();
401```
402
403### Precautions for Using Thread Functions
404In a multi-thread environment, avoid using shared data structures and global variables to avoid competition and conflicts. Ensure that threads are synchronized and mutually exclusive to avoid data inconsistency. In addition, pay attention to the following:
405* The calls to thread-safe functions are asynchronous, and the calls to the JavaScript callbacks are placed in the task queue.
406* When **napi_threadsafe_function** is called to create a thread-safe function, the **napi_finalize** callback can be provided. When the thread-safe function is about to be destroyed, the **napi_finalize** callback is invoked on the main thread.
407* The context is given when **napi_create_threadsafe_function** is called and can be obtained from any thread that calls **napi_get_threadsafe_function_context**.
408