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