• 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
11JavaScript虚拟机(JSVM)的快照创建功能,将当前运行时的JavaScript程序状态保存为一个快照文件,这个快照文件包含了当前的堆内存、执行上下文、函数闭包等信息。
12
13## 基本概念
14
15- **虚拟机启动快照**:虚拟机在某个特定时间点的状态快照,包含了当前虚拟机的所有内部状态和数据。通过创建一个启动快照,可以在之后的时间点恢复虚拟机到相同的状态。
16
17创建和使用虚拟机启动快照可以简化一些复杂的编程任务,提高JSVM中虚拟机的管理和维护效率,增强程序的灵活性和稳定性。
18
19## 接口说明
20
21| 接口                       | 功能说明                       |
22|----------------------------|-------------------------------|
23| OH_JSVM_CreateSnapshot     | 用于创建虚拟机的启动快照        |
24|OH_JSVM_CreateEnvFromSnapshot| 基于虚拟机的起始快照,创建一个新的环境 |
25## 使用示例
26
27### OH_JSVM_CreateSnapshot & OH_JSVM_CreateEnvFromSnapshot
28
29用于创建和使用虚拟机的启动快照。
30
31cpp部分代码:
32
33**注意事项**: 需要在OH_JSVM_Init的时候,将JSVM对外部的依赖注册到initOptions.externalReferences中。
34```cpp
35// hello.cpp
36#include "napi/native_api.h"
37#include "ark_runtime/jsvm.h"
38#include <hilog/log.h>
39#include <fstream>
40
41#define LOG_DOMAIN 0x0202
42#define LOG_TAG "TEST_TAG"
43
44static int g_aa = 0;
45
46#define CHECK_RET(theCall)                                                                           \
47    do {                                                                                             \
48        JSVM_Status cond = theCall;                                                                  \
49        if ((cond) != JSVM_OK) {                                                                     \
50            const JSVM_ExtendedErrorInfo *info;                                                      \
51            OH_JSVM_GetLastErrorInfo(env, &info);                                                    \
52            OH_LOG_ERROR(LOG_APP,                                                                    \
53                "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \
54                __FILE__,                                                                            \
55                __LINE__,                                                                            \
56                cond,                                                                                \
57                info != nullptr ? info->errorMessage : "");                                          \
58            return -1;                                                                               \
59        }                                                                                            \
60    } while (0)
61
62#define CHECK(theCall)                                                                                              \
63    do {                                                                                                            \
64        JSVM_Status cond = theCall;                                                                                 \
65        if ((cond) != JSVM_OK) {                                                                                    \
66            OH_LOG_ERROR(                                                                                           \
67                LOG_APP, "jsvm fail file: %{public}s line: %{public}d ret = %{public}d", __FILE__, __LINE__, cond); \
68            return -1;                                                                                              \
69        }                                                                                                           \
70    } while (0)
71
72// 用于调用theCall并检查其返回值是否为JSVM_OK。
73// 如果不是,则调用GET_AND_THROW_LAST_ERROR处理错误并返回retVal。
74#define JSVM_CALL_BASE(env, theCall, retVal)                                                         \
75    do {                                                                                             \
76        JSVM_Status cond = theCall;                                                                  \
77        if (cond != JSVM_OK) {                                                                       \
78            const JSVM_ExtendedErrorInfo *info;                                                      \
79            OH_JSVM_GetLastErrorInfo(env, &info);                                                    \
80            OH_LOG_ERROR(LOG_APP,                                                                    \
81                "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \
82                __FILE__,                                                                            \
83                __LINE__,                                                                            \
84                cond,                                                                                \
85                info != nullptr ? info->errorMessage : "");                                          \
86            return retVal;                                                                           \
87        }                                                                                            \
88    } while (0)
89
90// JSVM_CALL_BASE的简化版本,返回nullptr
91#define JSVM_CALL(theCall) JSVM_CALL_BASE(env, theCall, nullptr)
92
93static const int MAX_BUFFER_SIZE = 128;
94// CreateHelloString()函数需绑定到JSVM虚拟机, 用于OH_JSVM_CreateSnapshot虚拟机快照的正常创建
95static JSVM_Value CreateHelloString(JSVM_Env env, JSVM_CallbackInfo info)
96{
97    JSVM_Value outPut;
98    OH_JSVM_CreateStringUtf8(env, "Hello world!", JSVM_AUTO_LENGTH, &outPut);
99    return outPut;
100}
101// 提供外部引用的方式以便JavaScript环境可以调用绑定的函数
102static JSVM_CallbackStruct helloCb = {CreateHelloString, nullptr};
103
104static intptr_t externals[] = {
105    (intptr_t)&helloCb,
106    0,
107};
108
109static JSVM_Value RunVMScript(JSVM_Env env, std::string &src)
110{
111    // 打开handleScope作用域
112    JSVM_HandleScope handleScope;
113    OH_JSVM_OpenHandleScope(env, &handleScope);
114    JSVM_Value jsStr = nullptr;
115    OH_JSVM_CreateStringUtf8(env, src.c_str(), src.size(), &jsStr);
116    // 编译JavaScript代码
117    JSVM_Script script;
118    OH_JSVM_CompileScript(env, jsStr, nullptr, 0, true, nullptr, &script);
119    // 执行JavaScript代码
120    JSVM_Value result = nullptr;
121    OH_JSVM_RunScript(env, script, &result);
122    // 关闭handleScope作用域
123    OH_JSVM_CloseHandleScope(env, handleScope);
124    return result;
125}
126// OH_JSVM_CreateSnapshot的样例方法
127static void CreateVMSnapshot()
128{
129    // 创建JavaScript虚拟机实例,打开虚拟机作用域
130    JSVM_VM vm;
131    JSVM_CreateVMOptions vmOptions;
132    memset(&vmOptions, 0, sizeof(vmOptions));
133    // isForSnapshotting设置该虚拟机是否用于创建快照
134    vmOptions.isForSnapshotting = true;
135    OH_JSVM_CreateVM(&vmOptions, &vm);
136    JSVM_VMScope vmScope;
137    OH_JSVM_OpenVMScope(vm, &vmScope);
138    // 创建JavaScript环境,打开环境作用域
139    JSVM_Env env;
140    // 将native函数注册成JavaScript可调用的方法
141    JSVM_PropertyDescriptor descriptor[] = {
142        {"createHelloString", nullptr, &helloCb, nullptr, nullptr, nullptr, JSVM_DEFAULT},
143    };
144    OH_JSVM_CreateEnv(vm, 1, descriptor, &env);
145    JSVM_EnvScope envScope;
146    OH_JSVM_OpenEnvScope(env, &envScope);
147    // 使用OH_JSVM_CreateSnapshot创建虚拟机的启动快照
148    const char *blobData = nullptr;
149    size_t blobSize = 0;
150    JSVM_Env envs[1] = {env};
151    OH_JSVM_CreateSnapshot(vm, 1, envs, &blobData, &blobSize);
152    // 将snapshot保存到文件中
153    // 保存快照数据,/data/storage/el2/base/files/test_blob.bin为沙箱路径
154    // 以包名为com.example.jsvm为例,实际文件会保存到/data/app/el2/100/base/com.example.jsvm/files/test_blob.bin
155    std::ofstream file(
156        "/data/storage/el2/base/files/test_blob.bin", std::ios::out | std::ios::binary | std::ios::trunc);
157    file.write(blobData, blobSize);
158    file.close();
159    // 关闭并销毁环境和虚拟机
160    OH_JSVM_CloseEnvScope(env, envScope);
161    OH_JSVM_DestroyEnv(env);
162    OH_JSVM_CloseVMScope(vm, vmScope);
163    OH_JSVM_DestroyVM(vm);
164}
165
166static void RunVMSnapshot()
167{
168    // blobData的生命周期不能短于vm的生命周期
169    // 从文件中读取snapshot
170    std::vector<char> blobData;
171    std::ifstream file("/data/storage/el2/base/files/test_blob.bin", std::ios::in | std::ios::binary | std::ios::ate);
172    size_t blobSize = file.tellg();
173    blobData.resize(blobSize);
174    file.seekg(0, std::ios::beg);
175    file.read(blobData.data(), blobSize);
176    file.close();
177    OH_LOG_INFO(LOG_APP, "Test JSVM RunVMSnapshot read file blobSize = : %{public}ld", blobSize);
178    // 使用快照数据创建虚拟机实例
179    JSVM_VM vm;
180    JSVM_CreateVMOptions vmOptions;
181    memset(&vmOptions, 0, sizeof(vmOptions));
182    vmOptions.snapshotBlobData = blobData.data();
183    vmOptions.snapshotBlobSize = blobSize;
184    OH_JSVM_CreateVM(&vmOptions, &vm);
185    JSVM_VMScope vmScope;
186    OH_JSVM_OpenVMScope(vm, &vmScope);
187    // 从快照中创建环境env
188    JSVM_Env env;
189    OH_JSVM_CreateEnvFromSnapshot(vm, 0, &env);
190    JSVM_EnvScope envScope;
191    OH_JSVM_OpenEnvScope(env, &envScope);
192    // 执行js脚本,快照记录的env中定义了createHelloString()
193    std::string src = "createHelloString()";
194    JSVM_Value result = RunVMScript(env, src);
195    // 环境关闭前检查脚本运行结果
196    char str[MAX_BUFFER_SIZE];
197    OH_JSVM_GetValueStringUtf8(env, result, str, MAX_BUFFER_SIZE, nullptr);
198    if (strcmp(str, "Hello world!") != 0) {
199        OH_LOG_ERROR(LOG_APP, "jsvm fail file: %{public}s line: %{public}d", __FILE__, __LINE__);
200    }
201    // 关闭并销毁环境和虚拟机
202    OH_JSVM_CloseEnvScope(env, envScope);
203    OH_JSVM_DestroyEnv(env);
204    OH_JSVM_CloseVMScope(vm, vmScope);
205    OH_JSVM_DestroyVM(vm);
206    return;
207}
208
209static JSVM_Value AdjustExternalMemory(JSVM_Env env, JSVM_CallbackInfo info)
210{
211    // 在创建虚拟机快照时,如果存在对外部的依赖,需要在OH_JSVM_Init时,将外部依赖注册到initOptions.externalReferences212    // 创建虚拟机快照并将快照保存到文件中
213    CreateVMSnapshot();
214    // snapshot可以记录下特定的js执行环境,可以跨进程通过snapshot快速还原出js执行上下文环境
215    RunVMSnapshot();
216    JSVM_Value result = nullptr;
217    OH_JSVM_CreateInt32(env, 0, &result);
218    return result;
219}
220
221static JSVM_CallbackStruct param[] = {
222    {.data = nullptr, .callback = AdjustExternalMemory},
223};
224static JSVM_CallbackStruct *method = param;
225// AdjustExternalMemory方法别名,供JS调用
226static JSVM_PropertyDescriptor descriptor[] = {
227    {"adjustExternalMemory", nullptr, method, nullptr, nullptr, nullptr, JSVM_DEFAULT},
228};
229
230// 样例测试JS
231const char *srcCallNative = R"JS(adjustExternalMemory();)JS";
232
233static int32_t TestJSVM()
234{
235    JSVM_InitOptions initOptions = {0};
236    JSVM_VM vm;
237    JSVM_Env env = nullptr;
238    JSVM_VMScope vmScope;
239    JSVM_EnvScope envScope;
240    JSVM_HandleScope handleScope;
241    JSVM_Value result;
242    // 初始化JavaScript引擎实例
243    if (g_aa == 0) {
244        g_aa++;
245        initOptions.externalReferences = externals;
246        int argc = 0;
247        char **argv = nullptr;
248        initOptions.argc = &argc;
249        initOptions.argv = argv;
250        CHECK(OH_JSVM_Init(&initOptions));
251    }
252    // 创建JSVM环境
253    CHECK(OH_JSVM_CreateVM(nullptr, &vm));
254    CHECK(OH_JSVM_CreateEnv(vm, sizeof(descriptor) / sizeof(descriptor[0]), descriptor, &env));
255    CHECK(OH_JSVM_OpenVMScope(vm, &vmScope));
256    CHECK_RET(OH_JSVM_OpenEnvScope(env, &envScope));
257    CHECK_RET(OH_JSVM_OpenHandleScope(env, &handleScope));
258
259    // 通过script调用测试函数
260    JSVM_Script script;
261    JSVM_Value jsSrc;
262    CHECK_RET(OH_JSVM_CreateStringUtf8(env, srcCallNative, JSVM_AUTO_LENGTH, &jsSrc));
263    CHECK_RET(OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script));
264    CHECK_RET(OH_JSVM_RunScript(env, script, &result));
265
266    // 销毁JSVM环境
267    CHECK_RET(OH_JSVM_CloseHandleScope(env, handleScope));
268    CHECK_RET(OH_JSVM_CloseEnvScope(env, envScope));
269    CHECK(OH_JSVM_CloseVMScope(vm, vmScope));
270    CHECK(OH_JSVM_DestroyEnv(env));
271    CHECK(OH_JSVM_DestroyVM(vm));
272    return 0;
273}
274
275static napi_value RunTest(napi_env env, napi_callback_info info)
276{
277    TestJSVM();
278    return nullptr;
279}
280
281EXTERN_C_START
282static napi_value Init(napi_env env, napi_value exports)
283{
284    OH_LOG_INFO(LOG_APP, "JSVM Init");
285    napi_property_descriptor desc[] = {
286        {"runTest", nullptr, RunTest, nullptr, nullptr, nullptr, napi_default, nullptr},
287    };
288
289    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
290    return exports;
291}
292EXTERN_C_END
293
294static napi_module demoModule = {
295    .nm_version = 1,
296    .nm_flags = 0,
297    .nm_filename = nullptr,
298    .nm_register_func = Init,
299    .nm_modname = "entry",
300    .nm_priv = ((void *)0),
301    .reserved = {0},
302};
303
304extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
305```
306<!-- @[oh_jsvm_create_snapshot_and_create_env_from_snapshot](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/JSVMAPI/JsvmUsageGuide/UsageInstructionsOne/createsnapshot/src/main/cpp/hello.cpp) -->
307
308ArkTS侧示例代码:
309
310```ts
311@Entry
312@Component
313struct Index {
314  @State message: string = 'Hello World';
315
316  build() {
317    Row() {
318      Column() {
319        Text(this.message)
320          .fontSize(50)
321          .fontWeight(FontWeight.Bold)
322          .onClick(() => {
323            // runtest
324            napitest.runTest();
325          })
326      }
327      .width('100%')
328    }
329    .height('100%')
330  }
331}
332```
333执行结果
334在LOG中输出:
335```ts
336Test JSVM RunVMSnapshot read file blobSize = : 300064
337```
338多次点击屏幕,LOG中输出:
339```ts
340Test JSVM RunVMSnapshot read file blobSize = : 300176
341Test JSVM RunVMSnapshot read file blobSize = : 300064
342Test JSVM RunVMSnapshot read file blobSize = : 300160
343Test JSVM RunVMSnapshot read file blobSize = : 300032
344Test JSVM RunVMSnapshot read file blobSize = : 300176
345Test JSVM RunVMSnapshot read file blobSize = : 300048
346```
347上述执行结果是因为在读取快照文件时,blobSize 的值来源于快照文件的大小(通过 file.tellg() 获取)。快照文件的大小直接决定了 blobSize 的值,所以会输出不同的值。
348