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. 回调函数(JS主,Native从)中Native发生异常,需往JSVM中抛出异常 281 ```c++ 282 // Native主,JSVM从 283 void ThrowError() { 284 bool isPending = false; 285 if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) { 286 JSVM_Value error; 287 // 获取并清空JSVM异常 288 if (JSVM_OK == OH_JSVM_GetAndClearLastException((env), &error)) { 289 // 获取异常堆栈 290 JSVM_Value stack = nullptr; 291 CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "stack", &stack)); 292 293 JSVM_Value message = nullptr; 294 CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "message", &message)); 295 296 // 需实现将JS字符串转化为C++的std::string 297 std::string stackstr = stack? GetValueString(stack) : ""; 298 std::string messagestr = message? GetValueString(message) : ""; 299 // 抛出异常,需实现JSError 300 throw JSError(*this, messagestr, stackstr); 301 } 302 } 303 // 抛出异常,需实现JSError 304 throw JSError(*this, "JSVM Runtime: unkown execption"); 305 } 306 307 status = OH_JSVM_SetNamedProperty(env, object, "foo", string); 308 // JSVM-API调用失败,清空JS引擎实例pending的异常,抛出C++异常 309 if (status != JSVM_OK) { 310 ThrowError(); 311 } 312 ``` 313 3142. C++调用JSVM-API(Native主,JS从)失败,需清理JSVM中等待处理的异常,避免影响后续JSVM-API的执行,并设置C++异常处理分支(或抛出C++异常) 315 ``` 316 // JSVM主, Native从 317 void DoSomething() { 318 throw("Do something failed"); 319 } 320 321 // Demo1: 捕获到C++异常,抛出异常到JSVM中 322 JSVM_Value NativeFunctionExceptionDemo1(JSVM_Env env, JSVM_CallbackInfo info) { 323 try { 324 DoSomething(); 325 } catch (const char *ex) { 326 OH_JSVM_ThrowError(env, nullptr, ex); 327 return nullptr; 328 } 329 return nullptr; 330 } 331 332 // Demo2: JSVM-API调用失败,抛出异常到JSVM中 333 JSVM_Value NativeFunctionExceptionDemo2(JSVM_Env env, JSVM_CallbackInfo info) { 334 JSVM_Value JSBool = nullptr; 335 bool value = false; 336 auto status = OH_JSVM_GetValueBool(env, JSBool, &value); 337 if (status != JSVM_OK) { 338 OH_JSVM_ThrowError(env, nullptr, "Get bool value failed"); 339 return nullptr; 340 } 341 return nullptr; 342 } 343 344 // Demo3:JSVM-API调用失败且在调用过程中已向JSVM中添加等待处理的异常,则无需再向JSVM中抛出异常 345 JSVM_Value NativeFunctionExceptionDemo3(JSVM_Env env, JSVM_CallbackInfo info) { 346 std::string sourcecodestr = R"JS( 347 throw Error('Error throw from js'); 348 )JS"; 349 JSVM_Value sourcecodevalue = nullptr; 350 OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue); 351 JSVM_Script script; 352 auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script); 353 JSVM_Value result; 354 // 执行JS脚本,执行过程中抛出JS异常 355 status = OH_JSVM_RunScript(env, script, &result); 356 if (status != JSVM_OK) { 357 bool isPending = false; 358 // 如果已有异常,则无需再向JSVM中抛出异常; 359 // 如需处理并抛出新异常,需先处理JSVM中等待的异常 360 if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) { 361 return nullptr; 362 } 363 OH_JSVM_ThrowError(env, nullptr, "Runscript failed"); 364 return nullptr; 365 } 366 return nullptr; 367 } 368 369 // 绑定NativeFunction到JSVM中,省略 370 std::string sourcecodestr = R"JS( 371 // consolelog需用户实现 372 try { 373 // 调用Native函数 374 NativeFunction() 375 } catch (e) { 376 // 处理Native中产生的异常 377 consolelog(e.message) 378 } 379 )JS"; 380 JSVM_Value sourcecodevalue = nullptr; 381 OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue); 382 JSVM_Script script; 383 auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script); 384 OH_LOG_INFO(LOG_APP, "JSVM API TEST: %{public}d", (uint32_t)status); 385 JSVM_Value result; 386 // 执行JS脚本,JS调用Native方法 387 status = OH_JSVM_RunScript(env, script, &result); 388 ``` 389 390**注意事项**:回调函数中调用JSVM-API失败,如要向JSVM中抛异常,需保证JSVM中无等待处理的异常,也可以不抛出异常,JS的try-catch块可以捕获回调函数调用API失败产生的JS异常,见`NativeFunctionExceptionDemo3`。 391 392## 上下文绑定对象 393 394**【规则】**:调用JSVM-API生成的JS函数、对象需绑定到上下文中才能从JS侧访问,`OH_JSVM_CreateFunction`接口中的`const char *`参数为创建函数的属性`name`,不代表上下文中指向该函数的函数名。调用JSVM-API生成的类、对象同理。 395 396**示例** 397 398``` 399JSVM_Value JSFunc = nullptr; 400const char *name = "NativeFunction"; 401JSVM_CallbackStruct cb = {NativeFunction, nullptr}; 402// 创建JS函数,该函数的属性 "name" 为 "NativeFunction" 403OH_JSVM_CreateFunction(env, name, strlen(name), &cb, &JSFunc); 404// 绑定函数到上下文 405// 获取上下文的global对象 406JSVM_Value global = nullptr; 407OH_JSVM_GetGlobal(env, &global); 408// 创建JS字符串"FunctionNameInJSContext" 409JSVM_Value key = nullptr; 410OH_JSVM_CreateStringLatin1(env, "FunctionNameInJSContext", JSVM_AUTO_LENGTH, &key); 411// 设置global的属性"FunctionNameInJSContext"为JSFunc,将函数绑定到上下文中 412OH_JSVM_SetProperty(env, global, key, JSFunc); 413// 在JS中调用函数 414std::string sourcecodestr = R"JS( 415 // consolelog需用户实现 416 FunctionNameInJSContext() // 调用成功 417 consolelog(FunctionNameInJSContext.name) // 打印 "NativeFunction" 418 try { 419 NativeFunction() // 无法找到该函数,抛出异常 420 } catch (e) { 421 consolelog(e.message) 422 } 423)JS"; 424```