使用JSVM-API接口进行虚拟机快照相关开发
=======================

#### 简介

JavaScript虚拟机（JSVM）的快照创建功能，将当前运行时的JavaScript程序状态保存为一个快照文件，这个快照文件包含了当前的堆内存、执行上下文、函数闭包等信息。

#### 基本概念

*   **虚拟机启动快照**：虚拟机在某个特定时间点的状态快照，包含了当前虚拟机的所有内部状态和数据。通过创建一个启动快照，可以在之后的时间点恢复虚拟机到相同的状态。

创建和使用虚拟机启动快照可以简化一些复杂的编程任务，提高JSVM中虚拟机的管理和维护效率，增强程序的灵活性和稳定性。

#### 接口说明

| 接口 | 功能说明 |
| :-- | :-- |
| OH\_JSVM\_CreateSnapshot | 用于创建虚拟机的启动快照 |
| OH\_JSVM\_CreateEnvFromSnapshot | 基于虚拟机的起始快照，创建一个新的环境 |

#### 使用示例

#### \[h2\]OH\_JSVM\_CreateSnapshot & OH\_JSVM\_CreateEnvFromSnapshot

用于创建和使用虚拟机的启动快照。

cpp部分代码：

**注意事项**: 需要在OH\_JSVM\_Init的时候，将JSVM对外部的依赖注册到initOptions.externalReferences中。

#include "napi/native\_api.h"
#include "ark\_runtime/jsvm.h"
#include <hilog/log.h>
#include <fstream>

#define LOG\_DOMAIN 0x0202
#define LOG\_TAG "TEST\_TAG"

static int g\_aa = 0;

#define CHECK\_RET(theCall)                                                                           \\
    do {                                                                                             \\
        JSVM\_Status cond = theCall;                                                                  \\
        if ((cond) != JSVM\_OK) {                                                                     \\
            const JSVM\_ExtendedErrorInfo \*info;                                                      \\
            OH\_JSVM\_GetLastErrorInfo(env, &info);                                                    \\
            OH\_LOG\_ERROR(LOG\_APP,                                                                    \\
                "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \\
                \_\_FILE\_\_,                                                                            \\
                \_\_LINE\_\_,                                                                            \\
                cond,                                                                                \\
                info != nullptr ? info->errorMessage : "");                                          \\
            return -1;                                                                               \\
        }                                                                                            \\
    } while (0)

#define CHECK(theCall)                                                                                              \\
    do {                                                                                                            \\
        JSVM\_Status cond = theCall;                                                                                 \\
        if ((cond) != JSVM\_OK) {                                                                                    \\
            OH\_LOG\_ERROR(                                                                                           \\
                LOG\_APP, "jsvm fail file: %{public}s line: %{public}d ret = %{public}d", \_\_FILE\_\_, \_\_LINE\_\_, cond); \\
            return -1;                                                                                              \\
        }                                                                                                           \\
    } while (0)

// 用于调用theCall并检查其返回值是否为JSVM\_OK。
// 如果不是，则调用GET\_AND\_THROW\_LAST\_ERROR处理错误并返回retVal。
#define JSVM\_CALL\_BASE(env, theCall, retVal)                                                         \\
    do {                                                                                             \\
        JSVM\_Status cond = theCall;                                                                  \\
        if (cond != JSVM\_OK) {                                                                       \\
            const JSVM\_ExtendedErrorInfo \*info;                                                      \\
            OH\_JSVM\_GetLastErrorInfo(env, &info);                                                    \\
            OH\_LOG\_ERROR(LOG\_APP,                                                                    \\
                "jsvm fail file: %{public}s line: %{public}d ret = %{public}d message = %{public}s", \\
                \_\_FILE\_\_,                                                                            \\
                \_\_LINE\_\_,                                                                            \\
                cond,                                                                                \\
                info != nullptr ? info->errorMessage : "");                                          \\
            return retVal;                                                                           \\
        }                                                                                            \\
    } while (0)

// JSVM\_CALL\_BASE的简化版本，返回nullptr
#define JSVM\_CALL(theCall) JSVM\_CALL\_BASE(env, theCall, nullptr)

static const int MAX\_BUFFER\_SIZE = 128;
// CreateHelloString()函数需绑定到JSVM虚拟机, 用于OH\_JSVM\_CreateSnapshot虚拟机快照的正常创建
static JSVM\_Value CreateHelloString(JSVM\_Env env, JSVM\_CallbackInfo info)
{
    JSVM\_Value outPut;
    OH\_JSVM\_CreateStringUtf8(env, "Hello world!", JSVM\_AUTO\_LENGTH, &outPut);
    return outPut;
}
// 提供外部引用的方式以便JavaScript环境可以调用绑定的函数
static JSVM\_CallbackStruct helloCb = {CreateHelloString, nullptr};

static intptr\_t g\_externals\[\] = {
    (intptr\_t)&helloCb,
    0,
};

static JSVM\_Value RunVMScript(JSVM\_Env env, std::string &src)
{
    // 打开handleScope作用域
    JSVM\_HandleScope handleScope;
    OH\_JSVM\_OpenHandleScope(env, &handleScope);
    JSVM\_Value jsStr = nullptr;
    OH\_JSVM\_CreateStringUtf8(env, src.c\_str(), src.size(), &jsStr);
    // 编译JavaScript代码
    JSVM\_Script script;
    OH\_JSVM\_CompileScript(env, jsStr, nullptr, 0, true, nullptr, &script);
    // 执行JavaScript代码
    JSVM\_Value result = nullptr;
    OH\_JSVM\_RunScript(env, script, &result);
    // 关闭handleScope作用域
    OH\_JSVM\_CloseHandleScope(env, handleScope);
    return result;
}
// OH\_JSVM\_CreateSnapshot的样例方法
static void CreateVMSnapshot()
{
    // 创建JavaScript虚拟机实例,打开虚拟机作用域
    JSVM\_VM vm;
    JSVM\_CreateVMOptions vmOptions;
    memset(&vmOptions, 0, sizeof(vmOptions));
    // isForSnapshotting设置该虚拟机是否用于创建快照
    vmOptions.isForSnapshotting = true;
    OH\_JSVM\_CreateVM(&vmOptions, &vm);
    JSVM\_VMScope vmScope;
    OH\_JSVM\_OpenVMScope(vm, &vmScope);
    // 创建JavaScript环境,打开环境作用域
    JSVM\_Env env;
    // 将native函数注册成JavaScript可调用的方法
    JSVM\_PropertyDescriptor descriptor\[\] = {
        {"createHelloString", nullptr, &helloCb, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
    };
    OH\_JSVM\_CreateEnv(vm, 1, descriptor, &env);
    JSVM\_EnvScope envScope;
    OH\_JSVM\_OpenEnvScope(env, &envScope);
    // 使用OH\_JSVM\_CreateSnapshot创建虚拟机的启动快照
    const char \*blobData = nullptr;
    size\_t blobSize = 0;
    JSVM\_Env envs\[1\] = {env};
    OH\_JSVM\_CreateSnapshot(vm, 1, envs, &blobData, &blobSize);
    // 将snapshot保存到文件中
    // 保存快照数据，/data/storage/el2/base/files/test\_blob.bin为沙箱路径
    // 以包名为com.example.jsvm为例，实际文件会保存到/data/app/el2/100/base/com.example.jsvm/files/test\_blob.bin
    std::ofstream file("/data/storage/el2/base/files/test\_blob.bin",
                       std::ios::out | std::ios::binary | std::ios::trunc);
    file.write(blobData, blobSize);
    file.close();
    // 关闭并销毁环境和虚拟机
    OH\_JSVM\_CloseEnvScope(env, envScope);
    OH\_JSVM\_DestroyEnv(env);
    OH\_JSVM\_CloseVMScope(vm, vmScope);
    OH\_JSVM\_DestroyVM(vm);
}

static void RunVMSnapshot()
{
    // blobData的生命周期不能短于vm的生命周期
    // 从文件中读取snapshot
    std::vector<char> blobData;
    std::ifstream file("/data/storage/el2/base/files/test\_blob.bin", std::ios::in | std::ios::binary | std::ios::ate);
    size\_t blobSize = file.tellg();
    blobData.resize(blobSize);
    file.seekg(0, std::ios::beg);
    file.read(blobData.data(), blobSize);
    file.close();
    OH\_LOG\_INFO(LOG\_APP, "Test JSVM RunVMSnapshot read file blobSize = : %{public}ld", blobSize);
    // 使用快照数据创建虚拟机实例
    JSVM\_VM vm;
    JSVM\_CreateVMOptions vmOptions;
    memset(&vmOptions, 0, sizeof(vmOptions));
    vmOptions.snapshotBlobData = blobData.data();
    vmOptions.snapshotBlobSize = blobSize;
    OH\_JSVM\_CreateVM(&vmOptions, &vm);
    JSVM\_VMScope vmScope;
    OH\_JSVM\_OpenVMScope(vm, &vmScope);
    // 从快照中创建环境env
    JSVM\_Env env;
    OH\_JSVM\_CreateEnvFromSnapshot(vm, 0, &env);
    JSVM\_EnvScope envScope;
    OH\_JSVM\_OpenEnvScope(env, &envScope);
    // 执行js脚本，快照记录的env中定义了createHelloString()
    std::string src = "createHelloString()";
    JSVM\_Value result = RunVMScript(env, src);
    // 环境关闭前检查脚本运行结果
    char str\[MAX\_BUFFER\_SIZE\];
    OH\_JSVM\_GetValueStringUtf8(env, result, str, MAX\_BUFFER\_SIZE, nullptr);
    if (strcmp(str, "Hello world!") != 0) {
        OH\_LOG\_ERROR(LOG\_APP, "jsvm fail file: %{public}s line: %{public}d", \_\_FILE\_\_, \_\_LINE\_\_);
    }
    // 关闭并销毁环境和虚拟机
    OH\_JSVM\_CloseEnvScope(env, envScope);
    OH\_JSVM\_DestroyEnv(env);
    OH\_JSVM\_CloseVMScope(vm, vmScope);
    OH\_JSVM\_DestroyVM(vm);
    return;
}

static JSVM\_Value AdjustExternalMemory(JSVM\_Env env, JSVM\_CallbackInfo info)
{
    // 在创建虚拟机快照时，如果存在对外部的依赖，需要在OH\_JSVM\_Init时，将外部依赖注册到initOptions.externalReferences中
    // 创建虚拟机快照并将快照保存到文件中
    CreateVMSnapshot();
    // snapshot可以记录下特定的js执行环境，可以跨进程通过snapshot快速还原出js执行上下文环境
    RunVMSnapshot();
    JSVM\_Value result = nullptr;
    OH\_JSVM\_CreateInt32(env, 0, &result);
    return result;
}

static JSVM\_CallbackStruct param\[\] = {
    {.data = nullptr, .callback = AdjustExternalMemory},
};
static JSVM\_CallbackStruct \*method = param;
// AdjustExternalMemory方法别名，供JS调用
static JSVM\_PropertyDescriptor descriptor\[\] = {
    {"adjustExternalMemory", nullptr, method, nullptr, nullptr, nullptr, JSVM\_DEFAULT},
};

// 样例测试JS
const char \*SRC\_CALL\_NATIVE = R"JS(adjustExternalMemory();)JS";

static int32\_t TestJSVM()
{
    JSVM\_InitOptions initOptions = {0};
    JSVM\_VM vm;
    JSVM\_Env env = nullptr;
    JSVM\_VMScope vmScope;
    JSVM\_EnvScope envScope;
    JSVM\_HandleScope handleScope;
    JSVM\_Value result;
    // 初始化JavaScript引擎实例
    if (g\_aa == 0) {
        g\_aa++;
        initOptions.externalReferences = g\_externals;
        int argc = 0;
        char \*\*argv = nullptr;
        initOptions.argc = &argc;
        initOptions.argv = argv;
        CHECK(OH\_JSVM\_Init(&initOptions));
    }
    // 创建JSVM环境
    CHECK(OH\_JSVM\_CreateVM(nullptr, &vm));
    CHECK(OH\_JSVM\_OpenVMScope(vm, &vmScope));
    CHECK(OH\_JSVM\_CreateEnv(vm, sizeof(descriptor) / sizeof(descriptor\[0\]), descriptor, &env));
    CHECK\_RET(OH\_JSVM\_OpenEnvScope(env, &envScope));
    CHECK\_RET(OH\_JSVM\_OpenHandleScope(env, &handleScope));

    // 通过script调用测试函数
    JSVM\_Script script;
    JSVM\_Value jsSrc;
    CHECK\_RET(OH\_JSVM\_CreateStringUtf8(env, SRC\_CALL\_NATIVE, JSVM\_AUTO\_LENGTH, &jsSrc));
    CHECK\_RET(OH\_JSVM\_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script));
    CHECK\_RET(OH\_JSVM\_RunScript(env, script, &result));

    // 销毁JSVM环境
    CHECK\_RET(OH\_JSVM\_CloseHandleScope(env, handleScope));
    CHECK\_RET(OH\_JSVM\_CloseEnvScope(env, envScope));
    CHECK(OH\_JSVM\_DestroyEnv(env));
    CHECK(OH\_JSVM\_CloseVMScope(vm, vmScope));
    CHECK(OH\_JSVM\_DestroyVM(vm));
    return 0;
}

static napi\_value RunTest(napi\_env env, napi\_callback\_info info)
{
    TestJSVM();
    return nullptr;
}

EXTERN\_C\_START
static napi\_value Init(napi\_env env, napi\_value exports)
{
    OH\_LOG\_INFO(LOG\_APP, "JSVM Init");
    napi\_property\_descriptor desc\[\] = {
        {"runTest", nullptr, RunTest, nullptr, nullptr, nullptr, napi\_default, nullptr},
    };

    napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
    return exports;
}
EXTERN\_C\_END

static napi\_module demoModule = {
    .nm\_version = 1,
    .nm\_flags = 0,
    .nm\_filename = nullptr,
    .nm\_register\_func = Init,
    .nm\_modname = "createsnapshot",
    .nm\_priv = ((void \*)0),
    .reserved = {0},
};

extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void) { napi\_module\_register(&demoModule); }

ArkTS侧示例代码：

import napitest from 'libentry.so';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // runtest
            napitest.runTest();
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

执行结果

在LOG中输出：

Test JSVM RunVMSnapshot read file blobSize = : 300064

多次点击屏幕,LOG中输出:

Test JSVM RunVMSnapshot read file blobSize = : 300176
Test JSVM RunVMSnapshot read file blobSize = : 300064
Test JSVM RunVMSnapshot read file blobSize = : 300160
Test JSVM RunVMSnapshot read file blobSize = : 300032
Test JSVM RunVMSnapshot read file blobSize = : 300176
Test JSVM RunVMSnapshot read file blobSize = : 300048

上述执行结果是因为在读取快照文件时，blobSize 的值来源于快照文件的大小（通过 file.tellg() 获取）。快照文件的大小直接决定了 blobSize 的值，所以会输出不同的值。