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