1# JSVM-API使用规范 2<!--Kit: NDK Development--> 3<!--Subsystem: arkcompiler--> 4<!--Owner: @yuanxiaogou; @string_sz--> 5<!--Designer: @knightaoko--> 6<!--Tester: @test_lzz--> 7<!--Adviser: @fang-jinxu--> 8 9## 生命周期管理 10 11**【规则】** 合理使用`OH_JSVM_OpenHandleScope`和`OH_JSVM_CloseHandleScope`管理JSVM_Value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。 12 13每个JSVM_Value属于特定的HandleScope,HandleScope通过`OH_JSVM_OpenHandleScope`和`OH_JSVM_CloseHandleScope`来建立和关闭,HandleScope关闭后,所属的JSVM_Value就会自动释放。 14 15**注意事项**: 16 171. JSVM_Value必须在HandleScope打开后才可创建(Node-API无该限制),否则会造成应用崩溃; 182. JSVM_Value不能在其对应的HandleScope关闭后使用,如需持久化持有,需调用`OH_JSVM_CreateReference`转化为`JSVM_Ref`; 193. Scope(包括JSVM_VMScope、JSVM_EnvScope、JSVM_HandleScope)需逆序关闭,最先打开的Scope需最后关闭,否则可能造成应用崩溃; 20 21**Scope关闭错误示例**: 22```c++ 23// 未逆序关闭JSVM_VMScope,可能造成应用崩溃 24JSVM_VM vm; 25JSVM_CreateVMOptions options; 26OH_JSVM_CreateVM(&options, &vm); 27 28JSVM_VMScope vmScope1, vmScope2; 29OH_JSVM_OpenVMScope(vm, &vmScope1); 30OH_JSVM_OpenVMScope(vm, &vmScope2); 31 32// 正确顺序为先关闭vmScope2,再关闭vmScope1 33OH_JSVM_CloseVMScope(vm, vmScope1); 34OH_JSVM_CloseVMScope(vm, vmScope2); 35OH_JSVM_DestroyVM(vm); 36``` 37 38 39**C++使用封装**: 40 41```c++ 42class HandleScopeWrapper { 43 public: 44 HandleScopeWrapper(JSVM_Env env) : env(env) { 45 OH_JSVM_OpenHandleScope(env, &handleScope); 46 } 47 48 ~HandleScopeWrapper() { 49 OH_JSVM_CloseHandleScope(env, handleScope); 50 } 51 52 HandleScopeWrapper(const HandleScopeWrapper&) = delete; 53 HandleScopeWrapper& operator=(const HandleScopeWrapper&) = delete; 54 HandleScopeWrapper(HandleScopeWrapper&&) = delete; 55 void* operator new(size_t) = delete; 56 void* operator new[](size_t) = delete; 57 58 protected: 59 JSVM_Env env; 60 JSVM_HandleScope handleScope; 61}; 62``` 63 64**示例**: 65 66```c++ 67// 在for循环中频繁调用JSVM接口创建js对象时,要加handle_scope及时释放不再使用的资源。 68// 下面例子中,每次循环结束局部变量res的生命周期已结束,因此加scope及时释放其持有的js对象,防止内存泄漏 69// 每次for循环结束后,触发HandleScopeWrapper的析构函数,释放scope持有的js对象 70for (int i = 0; i < 100000; i++) 71{ 72 HandleScopeWrapper scope(env); 73 JSVM_Value res; 74 OH_JSVM_CreateObject(env, &res); 75 if (i == 1000) { 76 // break退出循环后会自动调用HandleScopeWrapper析构函数释放资源 77 break; 78 } 79} 80``` 81 82## 多引擎实例上下文敏感 83 84**【规则】** 多引擎实例(VM)场景下,禁止通过JSVM-API跨引擎实例访问JS对象。 85 86引擎实例是一个独立运行环境,JS对象创建访问等操作必须在同一个引擎实例中进行。若在不同引擎实例中操作同一个对象,可能会引发程序崩溃。引擎实例在接口中体现为JSVM_Env。 87 88**错误示例**: 89 90```c++ 91// 线程1执行,在env1创建string对象,值为"value1" 92OH_JSVM_CreateStringUtf8(env1, "value1", JSVM_AUTO_LENGTH , &string); 93// 线程2执行,在env2创建object对象,并将上述的string对象设置到object对象中 94JSVM_Status status = OH_JSVM_CreateObject(env2, &object); 95if (status != JSVM_OK) 96{ 97 return; 98} 99 100status = OH_JSVM_SetNamedProperty(env2, object, "string1", string); 101if (status != JSVM_OK) 102{ 103 return; 104} 105``` 106 107所有的JS对象都隶属于具体的某一JSVM_Env,不可将env1的对象,设置到env2中的对象中。在env2中一旦访问到env1的对象,程序可能会发生崩溃。 108 109## 多线程共享引擎实例 110 111**【规则】** 多线程同时使用同一个引擎实例的场景下,需要加锁使用。保证一个引擎实例在同一时刻只能在一个线程执行。多线程同一时刻同时使用引擎实例可能造成应用崩溃。 112 113**注意事项**: 114 1151. `OH_JSVM_IsLocked`的结果为**当前线程**是否持有引擎实例的锁,无需设置循环等待其他线程释放锁; 1162. `OH_JSVM_AcquireLock`在同一线程中嵌套使用不会造成死锁; 1173. 使用`OH_JSVM_ReleaseLock`时需判断是否在最外层,避免同一线程中嵌套使用`OH_JSVM_AcquireLock`的场景下内层释放了整个线程的锁; 1184. `OH_JSVM_AcquireLock`后需调用`OH_JSVM_OpenHandleScope`让引擎实例进入线程;`OH_JSVM_ReleaseLock`前需调用`OH_JSVM_CloseHandleScope`让引擎实例退出线程; 1195. 不同线程禁止嵌套使用引擎实例,如需临时切换线程使用引擎实例,请确保`JSVM_Value`已保存为`JSVM_Ref`,释放锁后对`JSVM_Value`将不可访问; 1206. 需注意资源获取的顺序为:锁 -> VMScope -> EnvScope -> HandleScope,资源释放的顺序正好相反,错误的顺序可能导致程序崩溃。 121 122**C++使用封装**: 123 124```c++ 125class LockWrapper { 126 public: 127 // 构造函数,获取锁、VMScope、EnvScope 128 LockWrapper(JSVM_Env env) : env(env) { 129 OH_JSVM_IsLocked(env, &isLocked); 130 if (!isLocked) { 131 OH_JSVM_AcquireLock(env); 132 OH_JSVM_GetVM(env, &vm); 133 OH_JSVM_OpenVMScope(vm, &vmScope); 134 OH_JSVM_OpenEnvScope(env, &envScope); 135 } 136 } 137 138 // 析构函数,释放EnvScope、VMScope、锁 139 ~LockWrapper() { 140 if (!isLocked) { 141 OH_JSVM_CloseEnvScope(env, envScope); 142 OH_JSVM_CloseVMScope(vm, vmScope); 143 OH_JSVM_ReleaseLock(env); 144 } 145 } 146 147 LockWrapper(const LockWrapper&) = delete; 148 LockWrapper& operator=(const LockWrapper&) = delete; 149 LockWrapper(LockWrapper&&) = delete; 150 void* operator new(size_t) = delete; 151 void* operator new[](size_t) = delete; 152 153 private: 154 JSVM_Env env; 155 JSVM_EnvScope envScope; 156 JSVM_VMScope vmScope; 157 JSVM_VM vm; 158 bool isLocked; 159}; 160``` 161 162 163 164**正确示例**: 165 166```c++ 167// 该用例演示了多线程中使用vm 168// t1线程先获取锁,并继续JSVM-API的调用 169// t2线程会在获取锁处阻塞,直到t1线程执行结束释放锁后,t2线程继续执行,调用JSVM-API接口 170static napi_value Add([[maybe_unused]] napi_env _env, [[maybe_unused]] napi_callback_info _info) { 171 static JSVM_VM vm; 172 static JSVM_Env env; 173 static int aa = 0; 174 if (aa == 0) { 175 OH_JSVM_Init(nullptr); 176 aa++; 177 // create vm 178 JSVM_CreateVMOptions options; 179 memset(&options, 0, sizeof(options)); 180 OH_JSVM_CreateVM(&options, &vm); 181 // create env 182 OH_JSVM_CreateEnv(vm, 0, nullptr, &env); 183 } 184 185 std::thread t1([]() { 186 LockWrapper lock(env); 187 JSVM_HandleScope handleScope; 188 OH_JSVM_OpenHandleScope(env, &handleScope); 189 JSVM_Value value; 190 JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical value 191 if (rst == JSVM_OK) { 192 OH_LOG_INFO(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 suc"); 193 } else { 194 OH_LOG_ERROR(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 fail"); 195 } 196 int32_t num1 = 0; 197 OH_JSVM_GetValueInt32(env, value, &num1); 198 OH_LOG_INFO(LOG_APP, "JSVM:t1 num1 = %{public}d", num1); 199 OH_JSVM_CloseHandleScope(env, handleScope); 200 }); 201 std::thread t2([]() { 202 LockWrapper lock(env); 203 JSVM_HandleScope handleScope; 204 OH_JSVM_OpenHandleScope(env, &handleScope); 205 JSVM_Value value; 206 JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical value 207 if (rst == JSVM_OK) { 208 OH_LOG_INFO(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 suc"); 209 } else { 210 OH_LOG_ERROR(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 fail"); 211 } 212 int32_t num1 = 0; 213 OH_JSVM_GetValueInt32(env, value, &num1); 214 OH_LOG_INFO(LOG_APP, "JSVM:t2 num1 = %{public}d", num1); 215 OH_JSVM_CloseHandleScope(env, handleScope); 216 }); 217 t1.detach(); 218 t2.detach(); 219 return nullptr; 220} 221``` 222 223## 获取JS传入参数及其数量 224 225**【规则】** 当传入OH_JSVM_GetCbInfo的argv不为nullptr时,argv的长度必须大于等于传入argc声明的大小。 226 227当argv不为nullptr时,OH_JSVM_GetCbInfo会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量,该接口仅会将声明的argc数量的参数写入argv;而当argc大于实际参数数量时,该接口会在argv的尾部填充undefined。 228 229**错误示例**: 230 231```cpp 232static JSVM_Value IncorrectDemo1(JSVM_Env env, JSVM_CallbackInfo info) { 233 // argc 未正确的初始化,其值为不确定的随机值,导致 argv 的长度可能小于 argc 声明的数量,数据越界。 234 size_t argc; 235 JSVM_Value argv[10] = {nullptr}; 236 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 237 return nullptr; 238} 239 240static JSVM_Value IncorrectDemo2(JSVM_Env env, JSVM_CallbackInfo info) { 241 // argc 声明的数量大于 argv 实际初始化的长度,导致 OH_JSVM_GetCbInfo 接口在写入 argv 时数据越界。 242 size_t argc = 5; 243 JSVM_Value argv[3] = {nullptr}; 244 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 245 return nullptr; 246} 247``` 248 249**正确示例**: 250 251```cpp 252static JSVM_Value GetArgvDemo1(napi_env env, JSVM_CallbackInfo info) { 253 size_t argc = 0; 254 // argv 传入 nullptr 来获取传入参数真实数量 255 OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr); 256 // JS 传入参数为0,不执行后续逻辑 257 if (argc == 0) { 258 return nullptr; 259 } 260 // 创建数组用以获取JS传入的参数 261 JSVM_Value* argv = new JSVM_Value[argc]; 262 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 263 // 业务代码 264 // ... ... 265 // argv 为 new 创建的对象,在使用完成后手动释放 266 delete[] argv; 267 return nullptr; 268} 269 270static JSVM_Value GetArgvDemo2(napi_env env, JSVM_CallbackInfo info) { 271 size_t argc = 2; 272 JSVM_Value* argv[2] = {nullptr}; 273 // OH_JSVM_GetCbInfo 会向 argv 中写入 argc 个 JS 传入参数或 undefined 274 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 275 // 业务代码 276 // ... ... 277 return nullptr; 278} 279``` 280 281## 异常处理 282 283**【建议】** JSVM-API接口调用发生异常需要及时处理,不能遗漏异常到后续逻辑,否则程序可能发生不可预期行为。 284 285 根据主从类型,异常处理可以分为两类: 286 2871. JSVM 执行 C++ 回调函数(JS主,Native从)时发生 C++ 异常,需往 JSVM 中抛出异常,下面用例描述了3种情况下 C++ 回调函数的写法。需要注意的是,回调函数中调用JSVM-API失败,如要向JSVM中抛异常,需保证JSVM中无等待处理的异常,也可以不抛出异常,JS的try-catch块可以捕获回调函数调用API失败产生的JS异常,见`NativeFunctionExceptionDemo3`。 288 ```c++ 289 // JSVM主, Native从 290 void DoSomething() { 291 throw("Do something failed"); 292 } 293 294 // Demo1: C++捕获到异常,抛出异常到JSVM中 295 JSVM_Value NativeFunctionExceptionDemo1(JSVM_Env env, JSVM_CallbackInfo info) { 296 try { 297 DoSomething(); 298 } catch (const char *ex) { 299 OH_JSVM_ThrowError(env, nullptr, ex); 300 return nullptr; 301 } 302 return nullptr; 303 } 304 305 // Demo2: JSVM-API调用失败,抛出异常到JSVM中 306 JSVM_Value NativeFunctionExceptionDemo2(JSVM_Env env, JSVM_CallbackInfo info) { 307 JSVM_Value JSBool = nullptr; 308 bool value = false; 309 auto status = OH_JSVM_GetValueBool(env, JSBool, &value); 310 if (status != JSVM_OK) { 311 OH_JSVM_ThrowError(env, nullptr, "Get bool value failed"); 312 return nullptr; 313 } 314 return nullptr; 315 } 316 317 // Demo3:JSVM-API调用失败且在调用过程中已向JSVM中添加等待处理的异常,则无需再向JSVM中抛出异常 318 JSVM_Value NativeFunctionExceptionDemo3(JSVM_Env env, JSVM_CallbackInfo info) { 319 std::string sourcecodestr = R"JS( 320 throw Error('Error throw from js'); 321 )JS"; 322 JSVM_Value sourcecodevalue = nullptr; 323 JSVM_CALL(OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue)); 324 JSVM_Script script; 325 auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script); 326 if (status != JSVM_OK) { 327 OH_JSVM_ThrowError(env, nullptr, "compile script failed"); 328 return nullptr; 329 } 330 JSVM_Value result; 331 // 执行JS脚本,执行过程中抛出JS异常 332 status = OH_JSVM_RunScript(env, script, &result); 333 if (status != JSVM_OK) { 334 bool isPending = false; 335 // 如果已有异常,则无需再向JSVM中抛出异常; 336 // 如需处理并抛出新异常,需先处理JSVM中等待的异常 337 if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) { 338 return nullptr; 339 } 340 OH_JSVM_ThrowError(env, nullptr, "Runscript failed"); 341 return nullptr; 342 } 343 return nullptr; 344 } 345 346 // 绑定NativeFunction到JSVM中,省略 347 std::string sourcecodestr = R"JS( 348 // consolelog需用户实现 349 try { 350 // 调用Native函数 351 NativeFunction() 352 } catch (e) { 353 // 处理Native中产生的异常 354 consolelog(e.message) 355 } 356 )JS"; 357 JSVM_Value sourcecodevalue = nullptr; 358 JSVM_CALL(OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue)); 359 JSVM_Script script; 360 JSVM_CALL(OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script)); 361 JSVM_Value result; 362 // 执行JS脚本,JS调用Native方法 363 JSVM_CALL(OH_JSVM_RunScript(env, script, &result)); 364 ``` 365 3662. C++调用JSVM-API(Native主,JS从)失败,需清理JSVM中等待处理的异常,避免影响后续JSVM-API的执行,并设置C++异常处理分支(或抛出C++异常)。 367 ```c++ 368 std::string sourcecodestr = R"JS( 369 throw Error('Error throw from js'); 370 )JS"; 371 JSVM_Value sourcecodevalue = nullptr; 372 OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue); 373 JSVM_Script script; 374 auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script); 375 // 异常处理分支 376 if (status != JSVM_OK) { 377 JSVM_Value error = nullptr; 378 // 获取并清理异常 379 JSVM_CALL(OH_JSVM_GetAndClearLastException((env), &error)); 380 // 处理异常,如打印信息,省略 381 // 抛出 C++ 异常或结束函数执行 382 throw "JS Compile Error"; 383 } 384 JSVM_Value result; 385 // 执行JS脚本,执行过程中抛出JS异常 386 status = OH_JSVM_RunScript(env, script, &result); 387 388 // 异常分支处理 389 if (status != JSVM_OK) { 390 JSVM_Value error = nullptr; 391 // 获取并清理异常 392 JSVM_CALL(OH_JSVM_GetAndClearLastException((env), &error)); 393 // 处理异常,如打印信息,省略 394 // 抛出 C++ 异常或结束函数执行 395 throw "JS RunScript Error"; 396 } 397 398 ``` 399 400## 上下文绑定对象 401 402**【规则】**:调用JSVM-API生成的JS函数、对象需绑定到上下文中才能从JS侧访问,`OH_JSVM_CreateFunction`接口中的`const char *`参数为创建函数的属性`name`,不代表上下文中指向该函数的名称。调用JSVM-API生成的类、对象同理。 403 404**示例**: 405 406```c++ 407JSVM_Value JSFunc = nullptr; 408const char *name = "NativeFunction"; 409JSVM_CallbackStruct cb = {NativeFunction, nullptr}; 410// 创建JS函数,该函数的属性 "name" 为 "NativeFunction" 411OH_JSVM_CreateFunction(env, name, strlen(name), &cb, &JSFunc); 412// 绑定函数到上下文 413// 获取上下文的global对象 414JSVM_Value global = nullptr; 415OH_JSVM_GetGlobal(env, &global); 416// 创建JS字符串"FunctionNameInJSContext" 417JSVM_Value key = nullptr; 418OH_JSVM_CreateStringLatin1(env, "FunctionNameInJSContext", JSVM_AUTO_LENGTH, &key); 419// 设置global的属性"FunctionNameInJSContext"为JSFunc,将函数绑定到上下文中 420OH_JSVM_SetProperty(env, global, key, JSFunc); 421// 在JS中调用函数 422std::string sourcecodestr = R"JS( 423 // consolelog需用户实现 424 FunctionNameInJSContext() // 调用成功 425 consolelog(FunctionNameInJSContext.name) // 打印 "NativeFunction" 426 try { 427 NativeFunction() // 无法找到该函数,抛出异常 428 } catch (e) { 429 consolelog(e.message) 430 } 431)JS"; 432```