• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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```