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```