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.externalReferences中 212 // 创建虚拟机快照并将快照保存到文件中 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