1# Node-API FAQs 2 3## What should I do when "undefined/not callable" or specific error message is reported for xxx after "import xxx from libxxx.so" is executed? 4 51. Check whether the module name in the .cpp file used in module registration is the same as that in the .so file name. 6 If the module name is **entry**, the .so file name must be **libentry.so**, and the **nm_modname** field in **napi_module** must be **entry**. The module names must be of the same case. 7 82. Check whether the .so file is successfully loaded. 9 10 Check the log related to module loading during the application startup. Search for the keyword "dlopen" and check for error information. Possible causes include the following: 11 12 - The file to be loaded does not exist or is in a blocklist. 13 - The application does not have the required permission. 14 - The **nm_modname** value does not match the module name in multi-thread scenarios (such as worker threads and taskpool). The module names must be of the same case. 153. Check whether the dependency .so files are successfully loaded. 16 Check that all the dependency .so files are packaged into the application and the application has the permission to open them. 17 184. Check whether the module import mode matches the .so file path. 19 If the module is imported by using **import xxx from '\@ohos.yyy.zzz'**, you may find **libzzz.z.so** or **libzzz_napi.z.so** in **/system/lib/module/yyy** (for a 32-bit system) or **/system/lib64/module/yyy** (for a 64-bit system). If the .so file does not exist or the file names do not match, an error containing the keyword "dlopen" will be reported. 20 21 22 23| **Error Log**| **Solution**| 24| -------- | -------- | 25| module $SO is not allowed to load in restricted runtime | The module, identified by **$SO**, is not allowed for the worker thread running in a restricted environment and cannot be loaded. You are advised to delete the module.| 26| module $SO is in blocklist, loading prohibited | The module, identified by **$SO**, is in the blocklist due to the control of the widget or Extension, and cannot be loaded. You are advised to delete the module.| 27| load module failed. $ERRMSG | The dynamic library fails to be loaded. **$ERRMSG** indicates the cause of the loading failure. Possible causes include the following:<br>- The .so file to be loaded does not exist.<br>- The dependency .so file does not exist. <br>- Undefined symbol is found. <br>Locate the cause based on the error message.| 28| try to load abc file from $FILEPATH failed. | You can load either a dynamic library or an .abc file. If this log information is displayed when you attempt to load a dynamic library, ignore this message. If it is displayed when you attempt to load an .abc file, the .abc file does not exist. **$FILEPATH** indicates the module path.| 29 305. If specific error message is reported, identify the fault based on the error message. 31 32| **Error message** | **Fault Analysis & Solution**| 33| -------- | -------- | 34| First attempt: $ERRMSG | Loading the .so file with the module name of "xxx" fails. *$ERRMSG* indicates the error information.| 35| Second attempt: $ERRMSG | Loading the .so file with the module name of "xxx_napi" fails. *$ERRMSG* indicates the error information.| 36| try to load abc file from xxx failed | Loading the .abc file fails. *xxx* indicates the name of the .abc file.| 37| module xxx is not allowed to load in restricted runtime. | This module cannot be used in restricted runtime. *xxx* indicates the module name. You are advised to delete the module.| 38| module xxx is in blocklist, loading prohibited. | The module cannot be used in the current extension. *xxx* indicates the module name. You are advised to delete the module.| 39 40## What should I do when an unexpected value is returned by an API and "occur exception need return" is reported? 41 42Before the call is complete, some Node-API interfaces are checked for JavaScript (JS) exceptions in the VM. If an exception is detected, "occur exception need return" will be reported, with the line number of the code and the Node-API interface name. 43 44You can solve this problem as follows: 45 46- If the exception does not matter, clear the exception. 47 Call **napi_get_and_clear_last_exception** before "occur exception need return" is printed to clear the exception. 48 49- Throw the exception to the ArkTS layer for capture. 50 Throw the exception directly to the ArkTS layer without going through the native logic. 51 52## What are the differences between the lifecycle of napi_value and napi_ref? 53 54- **native_value** is managed by **HandleScope**. Generally, you do not need to add **HandleScope** for **native_value** (except for **complete callback** of **uv_queue_work**). 55 56- **napi_ref** must be deleted manually. 57 58## How do I locate the fault if the return value of a Node-API interface is not "napi_ok"? 59 60When a Node-API interface is successfully executed, **napi_ok** is returned. If the return value is not **napi_ok**, locate the fault as follows: 61 62- Check the result of the input parameter null check, which is performed first before a Node-API interface is executed. The code is as follows: 63 64 ```cpp 65 CHECK_ENV: null check for env. 66 CHECK_ARG: null check for other input parameters. 67 ``` 68 69- Check the result of the input parameter type check, which is performed for certain Node-API interfaces. For example, **napi_get_value_double** is used to obtain a C double value from a JS number, and the type of the JS value passed in must be number. The parameter type check is as follows: 70 71 ```cpp 72 RETURN_STATUS_IF_FALSE(env, nativeValue->TypeOf() == NATIVE_NUMBER, napi_number_expected); 73 ``` 74 75- Check the return value, which contains the verification result of certain interfaces. For example, **napi_call_function** is used to execute a JS function. If an exception occurs in the JS function, Node-API returns **napi_pending_exception**. 76 77 ```cpp 78 auto resultValue = engine->CallFunction(nativeRecv, nativeFunc, nativeArgv, argc); 79 RETURN_STATUS_IF_FALSE(env, resultValue != nullptr, napi_pending_exception) 80 ``` 81 82- Determine the status value returned, and analyze the situation in which the status value is returned. 83 84## What should I do if memory leaks when napi_threadsafe_function is used? 85 86When **napi_threadsafe_function** (**tsfn** for short) is used, **napi_acquire_threadsafe_function** is often called to change the reference count of **tsfn** to ensure that **tsfn** is not released unexpectedly. When all the **tsfn** calls are complete, **napi_release_threadsafe_function** should be called in **napi_tsfn_release** mode in a timely manner to ensure that the reference count returns to the value before **napi_acquire_threadsafe_function** is called. **tsfn** can be correctly released only when the reference count is **0**. 87 88When **env** is about to exit but the reference count of **tsfn** is not **0**, **napi_release_threadsafe_function** should be called in **napi_tsfn_abort** mode to ensure that **tsfn** is not held or used by **env** after **env** is released. If **env** continues to hold and use **tsfn** after exiting, the application may crash. 89 90The following code shows how to register **env_cleanup** to ensure that **tsfn** is no longer held by **env** after **env** exits. 91 92```cpp 93//napi_init.cpp 94#include <hilog/log.h> // To output logs, link libhilog_ndk.z.so. 95#include <thread> // Include the thread module to create and manage threads. 96#include <unistd.h> // Include unistd.h to suspend the execution of the calling thread. 97 98// Define the log domain and tag. 99#undef LOG_DOMAIN 100#undef LOG_TAG 101#define LOG_DOMAIN 0x2342 102#define LOG_TAG "MY_TSFN_DEMO" 103 104/* 105 To construct a scenario in which the env lifecycle is shorter than the native lifecycle, 106 the following uses worker, taskpool, and napi_create_ark_runtime 107 to create an ArkTS running environment for a worker thread and manually stop the thread in advance. 108*/ 109 110 111// Define a struct to simulate the scenario where tsfn is stored. 112class MyTsfnContext { 113public: 114// MyTsfnContext is constructed only in a JS thread because Node-API is used. 115MyTsfnContext(napi_env env, napi_value workName) { 116 // Register the env_cleanup_hook function. 117 napi_add_env_cleanup_hook(env, Cleanup, this); 118 // Create a thread-safe function. 119 if (napi_create_threadsafe_function(env, nullptr, nullptr, workName, 1, 1, this, 120 TsfnFinalize, this, TsfnCallJs, &tsfn_) != napi_ok) { 121 OH_LOG_INFO(LOG_APP, "tsfn is created failed"); 122 return; 123 }; 124}; 125 126~MyTsfnContext() { OH_LOG_INFO(LOG_APP, "MyTsfnContext is deconstructed"); }; 127 128napi_threadsafe_function GetTsfn() { 129 std::unique_lock<std::mutex> lock(mutex_); 130 return tsfn_; 131} 132 133bool Acquire() { 134 if (GetTsfn() == nullptr) { 135 return false; 136 }; 137 return (napi_acquire_threadsafe_function(GetTsfn()) == napi_ok); 138}; 139 140bool Release() { 141 if (GetTsfn() == nullptr) { 142 return false; 143 }; 144 return (napi_release_threadsafe_function(GetTsfn(), napi_tsfn_release) == napi_ok); 145}; 146 147bool Call(void *data) { 148 if (GetTsfn() == nullptr) { 149 return false; 150 }; 151 return (napi_call_threadsafe_function(GetTsfn(), data, napi_tsfn_blocking) == napi_ok); 152}; 153 154private: 155// Ensure correct read and write of tsfn by multiple threads. 156std::mutex mutex_; 157napi_threadsafe_function tsfn_ = nullptr; 158 159// Call napi_add_env_cleanup_hook. 160static void Cleanup(void *data) { 161 MyTsfnContext *that = reinterpret_cast<MyTsfnContext *>(data); 162 napi_threadsafe_function tsfn = that->GetTsfn(); 163 std::unique_lock<std::mutex> lock(that->mutex_); 164 that->tsfn_ = nullptr; 165 lock.unlock(); 166 OH_LOG_WARN(LOG_APP, "cleanup is called"); 167 napi_release_threadsafe_function(tsfn, napi_tsfn_abort); 168}; 169 170// Callback to be invoked when tsfn is released. 171static void TsfnFinalize(napi_env env, void *data, void *hint) { 172 MyTsfnContext *ctx = reinterpret_cast<MyTsfnContext *>(data); 173 OH_LOG_INFO(LOG_APP, "tsfn is released"); 174 napi_remove_env_cleanup_hook(env, MyTsfnContext::Cleanup, ctx); 175 // Cleanup releases the thread-safe function in advance. To avoid UAF, enable the caller to trigger the release. 176 if (ctx->GetTsfn() != nullptr) { 177 OH_LOG_INFO(LOG_APP, "ctx is released"); 178 delete ctx; 179 } 180}; 181 182// Callback sent by tsfn to the JS thread for execution. 183static void TsfnCallJs(napi_env env, napi_value func, void *context, void *data) { 184 MyTsfnContext *ctx = reinterpret_cast<MyTsfnContext *>(context); 185 char *str = reinterpret_cast<char *>(data); 186 OH_LOG_INFO(LOG_APP, "tsfn is called, data is: \"%{public}s\"", str); 187 // The service logic is omitted here. 188}; 189}; 190 191// Register the myTsfnDemo method with the module Index.d.ts. The myTsfnDemo method is defined as follows: 192// export const myTsfnDemo: () => void; 193napi_value MyTsfnDemo(napi_env env, napi_callback_info info) { 194 OH_LOG_ERROR(LOG_APP, "MyTsfnDemo is called"); 195 napi_value workName = nullptr; 196 napi_create_string_utf8(env, "MyTsfnWork", NAPI_AUTO_LENGTH, &workName); 197 MyTsfnContext *myContext = new MyTsfnContext(env, workName); 198 if (myContext->GetTsfn() == nullptr) { 199 OH_LOG_ERROR(LOG_APP, "failed to create tsfn"); 200 delete myContext; 201 return nullptr; 202 }; 203 char *data0 = new char[]{"Im call in ArkTS Thread"}; 204 if (!myContext->Call(data0)) { 205 OH_LOG_INFO(LOG_APP, "call tsfn failed"); 206 }; 207 208 // Create a thread to simulate an asynchronous operation. 209 std::thread( 210 [](MyTsfnContext *myCtx) { 211 if (!myCtx->Acquire()) { 212 OH_LOG_ERROR(LOG_APP, "acquire tsfn failed"); 213 return; 214 }; 215 char *data1 = new char[]{"Im call in std::thread"}; 216 // This operation is optional and used only to check whether the asynchronous tsfn is still valid. 217 if (!myCtx->Call(data1)) { 218 OH_LOG_ERROR(LOG_APP, "call tsfn failed"); 219 }; 220 // Suspend the thread for 5 seconds to simulate a time-consuming operation, which is not complete when env exits. 221 sleep(5); 222 // When the asynchronous operation is complete, tsfn has been released and set to nullptr. 223 char *data2 = new char[]{"Im call after work"}; 224 if (!myCtx->Call(data2) && !myCtx->Release()) { 225 OH_LOG_ERROR(LOG_APP, "call and release tsfn failed"); 226 delete myCtx; 227 } 228 }, 229 myContext) 230 .detach(); 231 return nullptr; 232}; 233``` 234 235The following is the main thread logic, which creates worker threads and instruct workers to execute tasks. 236 237```ts 238// Main thread Index.ets 239import worker, { MessageEvents } from '@ohos.worker'; 240 241const mWorker = new worker.ThreadWorker('../workers/Worker'); 242mWorker.onmessage = (e: MessageEvents) => { 243 const action: string | undefined = e.data?.action; 244 if (action === 'kill') { 245 mWorker.terminate(); 246 } 247} 248 249// The registration of the triggering mode is omitted. 250mWorker.postMessage({action: 'tsfn-demo'}) 251 252``` 253 254The following is the worker thread logic, which triggers native tasks. 255 256```ts 257// worker.ets 258import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; 259import napiModule from 'libentry.so'; // libentry.so is the module name of the Node-API library. 260 261const workerPort: ThreadWorkerGlobalScope = worker.workerPort; 262 263workerPort.onmessage = (e: MessageEvents) => { 264 const action: string | undefined = e.data?.action; 265 if (action === 'tsfn-demo') { 266 // Trigger the tsfn demo in C++. 267 napiModule.myTsfnDemo(); 268 // Instruct the main thread to terminate the worker. 269 workerPort.postMessage({action: 'kill'}); 270 }; 271} 272``` 273 274## napi_get_uv_event_loop Error Codes 275 276Additional parameter verification is added to prevent use of invalid **napi_env** in **napi_get_uv_event_loop**. The return value indicates the verification result. The return values of this API are as follows: 277 2781. If **env** and/or **loop** are **nullptr**, **napi_invalid_arg** is returned. 2792. If **env** is a valid **napi_env** and **loop** is a valid pointer, **napi_ok** is returned. 2803. If **env** is not a valid **napi_env** (for example, a released **env**), **napi_generic_failure** is returned. 281 282Example: 283 284```c++ 285napi_value NapiInvalidArg(napi_env env, napi_callback_info) 286{ 287 napi_status status = napi_ok; 288 status = napi_get_uv_event_loop(env, nullptr); // loop is nullptr, napi_invalid_arg. 289 if (status == napi_ok) { 290 // do something 291 } 292 293 uv_loop_s* loop = nullptr; 294 status = napi_get_uv_event_loop(nullptr, &loop); // env is nullptr, napi_invalid_arg. 295 if (status == napi_ok) { 296 // do something 297 } 298 299 status = napi_get_uv_event_loop(nullptr, nullptr); // Both env and loop are nullptr, napi_invalid_arg. 300 if (status == napi_ok) { 301 // do something 302 } 303 304 return nullptr; 305} 306 307napi_value NapiGenericFailure(napi_env env, napi_callback_info) 308{ 309 std::thread([]() { 310 napi_env env = nullptr; 311 napi_create_ark_runtime (&env); // Generally, the return value needs to be checked. 312 // napi_destroy_ark_runtime sets the pointer to null. Copy the pointer to simulate the problem. 313 napi_env copiedEnv = env; 314 napi_destroy_ark_runtime(&env); 315 uv_loop_s* loop = nullptr; 316 napi_status status = napi_get_uv_event_loop(copiedEnv, &loop); // env is invalid. napi_generic_failure will be returned. 317 if (status == napi_ok) { 318 // do something 319 } 320 }).detach();; 321} 322``` 323