HarmonyOS Next开发学习手册——JSVM-API(一)

场景介绍

HarmonyOS JSVM-API是基于标准JS引擎提供的一套稳定的ABI,为开发者提供了较为完整的JS引擎能力,包括创建和销毁引擎,执行JS代码,JS/C++交互等关键能力。

通过JSVM-API,开发者可以在应用运行期间直接执行一段动态加载的JS代码。也可以选择将一些对性能、底层系统调用有较高要求的核心功能用C/C++实现并将C++方法注册到JS侧,在JS代码中直接调用,提高应用的执行效率。

本文中如无特别说明,后续均使用JSVM-API指代HarmonyOS JSVM-API能力。

JSVM-API的组成架构

图1 JSVM-API的组成架构

  • Native Module:开发者使用JSVM-API开发的模块,用于在Native侧使用。
  • VM Life Cycle Manager:管理JSVM_VM的生命周期。
  • JS Context Manager:管理JSVM_Env的生命周期。
  • Context Snapshot:上下文快照,可用以缩短JS Context的创建时间。
  • JS Code Execute:执行JS代码。
  • JS/C++ Interaction:连接JS层与C++层,用于支撑JS与C++之间的交互。
  • Code Cache:编译后的JS代码的缓存,能提升JS代码执行的启动速度。
  • Debugger:调试器,用于调试JS代码。
  • CPU Profiler:该工具能记录JS代码执行所用的时间,使用此工具能帮助开发者分析JS代码的性能瓶颈,为代码优化提供数据支撑。
  • Heap Snapshot:JS堆内存分析/调优工具,可以进行内存优化和发现内存泄漏问题。
  • Heap Statistics:JS堆统计信息,包括内存大小及上下文数量。
  • Memory Adjustment:调整外部内存大小、虚拟机内存压力,以加快触发GC。
  • VM Information:JSVM_VM的信息。
  • Standard JS Engine:标准JS引擎。

JSVM-API的关键交互流程

图2 JSVM-API的关键交互流程

JSVM-API和Native模块之间的交互流程,主要分为以下两步:

  1. 初始化阶段:在Native模块上初始化JSVM和JS上下文,并完成Native函数的注册。Native方法将会被挂载到JS执行环境的全局上下文即GlobalThis。
  2. 调用阶段:当JS侧调用通过JSVM-API注册到JS全局上下文的方法时,JS引擎会找到并调用对应的C/C++方法。

JSVM-API的数据类型

JSVM_Status

是一个枚举数据类型,表示JSVM-API接口返回的状态信息。

每当调用一个JSVM-API函数,都会返回该值,表示操作成功与否的相关信息。

typedef enum {
    JSVM_OK,
    JSVM_INVALID_ARG,
    JSVM_OBJECT_EXPECTED,
    JSVM_STRING_EXPECTED,
    JSVM_NAME_EXPECTED,
    JSVM_FUNCTION_EXPECTED,
    JSVM_NUMBER_EXPECTED,
    JSVM_BOOL_EXPECTED,
    JSVM_ARRAY_EXPECTED,
    JSVM_GENERIC_FAILURE,
    JSVM_PENDING_EXCEPTION,
    JSVM_CENCELLED,
    JSVM_ESCAPE_CALLED_TWICE,
    JSVM_HANDLE_SCOPE_MISMATCH,
    JSVM_CALLBACK_SCOPE_MISMATCH,
    JSVM_QUEUE_FULL,
    JSVM_CLOSING,
    JSVM_BIGINT_EXPECTED,
    JSVM_DATA_EXPECTED,
    JSVM_CALLBACK_SCOPE_MISMATCH,
    JSVM_DETACHABLE_ARRAYBUFFER_EXPECTED,
    JSVM_WOULD_DEADLOCK,  /* unused */
    JSVM_NO_EXTERNAL_BUFFERS_ALLOWED,
    JSVM_CANNOT_RUN_JS
} JSVM_Status;

JSVM_ExtendedErrorInfo

一个结构体,在调用函数不成功时存储了较为详细的错误信息。

typedef struct {
    const char* errorMessage;
    void* engineReserved;
    uint32_t engineErrorCode;
    JSVM_Status errorCode;
} JSVM_ExtendedErrorInfo;

JSVM_Value

在C++代码中,表示一个JavaScript值。

JSVM_Env

  • 用于表示JSVM-API执行时的上下文,Native侧函数入参,并传递给函数中的JSVM-API接口。
  • 退出Native侧插件时,JSVM_Env将失效,该事件通过回调传递给OH_JSVM_SetInstanceData。
  • 禁止缓存JSVM_Env,禁止在不同Worker中传递JSVM_Env。
  • 在不同线程间共享JSVM_Env时,要保证在线程切换时在前一个线程中关闭env scope并在新的线程中打开新的env scope,以保证threadlocal变量的线程隔离。

JSVM_ValueType

JSVM_Value的类型。包含了ECMAScript语言规范中定义的类型,其中JSVM_EXTERNAL表示外部数据类型。

typedef enum {
    JSVM_UNDEFINED,
    JSVM_NULL,
    JSVM_BOOLEAN,
    JSVM_NUMBER,
    JSVM_STRING,
    JSVM_SYMBOL,
    JSVM_OBJECT,
    JSVM_FUNCTION,
    JSVM_EXTERNAL,
    JSVM_BIGINT,
} JSVM_ValueType;

JSVM_TypedarrayType

TypedArray的基本二进制标量数据类型。

typedef enum {
    JSVM_INT8_ARRAY,
    JSVM_UINT8_ARRAY,
    JSVM_UINT8_CLAMPED_ARRAY,
    JSVM_INT16_ARRAY,
    JAVM_UINT16_ARRAY,
    JSVM_INT32_ARRAY,
    JSVM_UINT32_ARRAY,
    JSVM_FLOAT32_ARRAY,
    JSVM_FLOAT64_ARRAY,
    JSVM_BIGINT64_ARRAY,
    JSVM_BIGUINT64_ARRAY,
} JSVM_TypedarrayType;

内存管理类型

JSVM-API包含以下内存管理类型:

JSVM_HandleScope

JSVM_HandleScope数据类型是用来管理JavaScript对象的生命周期的。它允许JavaScript对象在一定范围内保持活动状态,以便在JavaScript代码中使用。在创建JSVM_HandleScope时,所有在该范围内创建的JavaScript对象都会保持活动状态,直到结束。这样可以避免在JavaScript代码中使用已经被释放的对象,从而提高代码的可靠性和性能

JSVM_EscapableHandleScope

  • 由OH_JSVM_OpenEscapableHandleScope接口创建,由OH_JSVM_CloseEscapableHandleScope接口关闭。
  • 表示一种特殊类型的句柄范围,用于将在JSVM_EscapableHandleScope范围内创建的值返回给父scope。
  • 用于OH_JSVM_EscapeHandle接口,将JSVM_EscapableHandleScope提升到JavaScript对象,以便在外部作用域使用。

JSVM_Ref

指向JSVM_Value,允许用户管理JavaScript值的生命周期。

JSVM_TypeTag

该结构体定义了一个包含两个无符号64位整数的类型标签,用于标识一个JSVM-API值的类型信息。

typedef struct {
    uint64_t lower;
    uint64_t upper;
} JSVM_TypeTag;
  • 存储了两个无符号64位整数的128位值,用它来标记JavaScript对象,确保它们属于某种类型。
  • 比OH_JSVM_Instanceof更强的类型检查,如果对象的原型被操纵,OH_JSVM_Instanceof可能会报告误报。
  • JSVM_TypeTag 在与 OH_JSVM_Wrap 结合使用时最有用,因为它确保从包装对象检索的指针可以安全地转换为与先前应用于JavaScript对象的类型标记相对应的Native类型。

回调类型

JSVM-API包含以下回调类型:

JSVM_CallbackInfo

表示用户定义的Native函数,暴露给JavaScript,即JS侧调用的接口;一般不在此Callback中创建Handle或者CallbackScope。

JSVM_CallbackStruct

用户提供的Native函数的回调函数指针和数据,JSVM_CallbackStruct将通过JSVM-API暴露给JavaScript。

typedef struct {
  JSVM_Value(*callback)(JSVM_Env env, JSVM_CallbackInfo info);
  void* data;
} JSVM_CallbackStruct;

JSVM_Callback

表示用户定义的Native函数,暴露给JavaScript,即JS侧调用的接口;除非在对象生命周期管理中有特殊要求,一般不在此callback中创建handle或者callback scope。

基本用法如下:

typedef JSVM_CallbackStruct* JSVM_Callback;

JSVM_Finalize

函数指针,用于传入OH_JSVM_SetInstanceData、OH_JSVM_CreateExternal、OH_JSVM_Wrap等接口。JSVM_Finalize在对象被回收时会被调用,可用于在JavaScript对象被垃圾回收时释放Native对象。

写法如下:

typedef void (JSVM_Finalize)(JSVM_Env env, void finalizeData, void* finalizeHint);

JSVM_PropertyHandlerConfigurationStruct

当执行对象的getter、setter、deleter和enumerator作时,对应的的回调将会触发。

typedef struct {
    JSVM_Value(JSVM_CDECL* genericNamedPropertyGetterCallback)(JSVM_Env env,
                                                               JSVM_Value name,
                                                               JSVM_Value thisArg,
                                                               JSVM_Value namedPropertyData);
    JSVM_Value(JSVM_CDECL* genericNamedPropertySetterCallback)(JSVM_Env env,
                                                               JSVM_Value name,
                                                               JSVM_Value property,
                                                               JSVM_Value thisArg,
                                                               JSVM_Value namedPropertyData);
    JSVM_Value(JSVM_CDECL* genericNamedPropertyDeleterCallback)(JSVM_Env env,
                                                                JSVM_Value name,
                                                                JSVM_Value thisArg,
                                                                JSVM_Value namedPropertyData);
    JSVM_Value(JSVM_CDECL* genericNamedPropertyEnumeratorCallback)(JSVM_Env env,
                                                                   JSVM_Value thisArg,
                                                                   JSVM_Value namedPropertyData);
    JSVM_Value(JSVM_CDECL* genericIndexedPropertyGetterCallback)(JSVM_Env env,
                                                                JSVM_Value index,
                                                                JSVM_Value thisArg,
                                                                JSVM_Value indexedPropertyData);
    JSVM_Value(JSVM_CDECL* genericIndexedPropertySetterCallback)(JSVM_Env env,
                                                                 JSVM_Value index,
                                                                 JSVM_Value property,
                                                                 JSVM_Value thisArg,
                                                                 JSVM_Value indexedPropertyData);
    JSVM_Value(JSVM_CDECL* genericIndexedPropertyDeleterCallback)(JSVM_Env env,
                                                                  JSVM_Value index,
                                                                  JSVM_Value thisArg,
                                                                  JSVM_Value indexedPropertyData);
    JSVM_Value(JSVM_CDECL* genericIndexedPropertyEnumeratorCallback)(JSVM_Env env,
                                                                     JSVM_Value thisArg,
                                                                     JSVM_Value indexedPropertyData);
    JSVM_Value namedPropertyData;
    JSVM_Value indexedPropertyData;
} JSVM_PropertyHandlerConfigurationStruct;

JSVM_PropertyHandlerCfg

包含属性监听回调的结构的指针类型。

基本用法如下:

typedef JSVM_PropertyHandlerConfigurationStruct* JSVM_PropertyHandlerCfg;

支持的JSVM-API接口

标准JS引擎的能力通过JSVM-API提供。JSVM-API支持动态链接到不同版本的JS引擎库,从而为开发者屏蔽掉不同引擎接口的差异。JSVM-API提供引擎生命周期管理、JS context管理、JS代码执行、JS/C++互操作、执行环境快照、codecache等能力,具体可见下文。

使用 JSVM-API 接口创建引擎实例及 JS 执行上下文环境

场景介绍

执行JS代码需要先创建JavaScript VM,创建JS执行的上下文环境。

接口说明

接口 功能说明
OH_JSVM_Init 初始化JavaScript引擎实例
OH_JSVM_CreateVM 创建JavaScript引擎实例
OH_JSVM_DestroyVM 销毁JavaScript引擎实例
OH_JSVM_OpenVMScope 打开一个新的VM scope,引擎实例只能在scope范围内使用,可以保证引擎实例不被销毁
OH_JSVM_CloseVMScope 关闭VM scope
OH_JSVM_CreateEnv 创建一个新的JS执行上下文环境,并注册指定的Native函数
OH_JSVM_DestroyEnv 销毁一个JS执行上下文环境
OH_JSVM_OpenEnvScope 打开一个新的Env scope,Env只能在scope范围内使用
OH_JSVM_CloseEnvScope 关闭Env scope
OH_JSVM_OpenHandleScope 打开一个Handle scope,确保scope范围内的JSVM_Value不被GC回收
OH_JSVM_CloseHandleScope 关闭Handle scope

场景示例

创建及销毁JavaScript引擎实例,包含创建及销毁JS执行上下文环境

bool VM_INIT = false;

static JSVM_Value ConsoleInfo(JSVM_Env env, JSVM_CallbackInfo info) {
    size_t argc = 1;
    JSVM_Value args[1];
    char log[256] = "";
    size_t logLength;
    OH_JSVM_GetCbInfo(env, info, &argc, args, NULL, NULL);

    OH_JSVM_GetValueStringUtf8(env, args[0], log, 255, &logLength);
    log[255] = 0;
    OH_LOG_INFO(LOG_APP, "JSVM API TEST: %{public}s", log);
    return nullptr;
}

static JSVM_Value Add(JSVM_Env env, JSVM_CallbackInfo info) {
    size_t argc = 2;
    JSVM_Value args[2];
    OH_JSVM_GetCbInfo(env, info, &argc, args, NULL, NULL);
    double num1, num2;
    env, OH_JSVM_GetValueDouble(env, args[0], &num1);
    OH_JSVM_GetValueDouble(env, args[1], &num2);
    JSVM_Value sum = nullptr;
    OH_JSVM_CreateDouble(env, num1 + num2, &sum);
    return sum;
}

static napi_value MyJSVMDemo([[maybe_unused]] napi_env _env, [[maybe_unused]] napi_callback_info _info) {
    std::thread t([]() {
        if (!VM_INIT) {
            // JSVM only need init once
            JSVM_InitOptions initOptions;
            memset(&initOptions, 0, sizeof(initOptions));
            OH_JSVM_Init(&initOptions);
            VM_INIT = true;
        }
        // create vm, and open vm scope
        JSVM_VM vm;
        JSVM_CreateVMOptions options;
        memset(&options, 0, sizeof(options));
        OH_JSVM_CreateVM(&options, &vm);
        JSVM_VMScope vmScope;
        OH_JSVM_OpenVMScope(vm, &vmScope);
        JSVM_CallbackStruct param[] = {
            {.data = nullptr, .callback = ConsoleInfo},
            {.data = nullptr, .callback = Add},
        };
        JSVM_PropertyDescriptor descriptor[] = {
            {"consoleinfo", NULL, &param[0], NULL, NULL, NULL, JSVM_DEFAULT},
            {"add", NULL, &param[1], NULL, NULL, NULL, JSVM_DEFAULT},
        };
        // create env, register native method, and open env scope
        JSVM_Env env;
        OH_JSVM_CreateEnv(vm, sizeof(descriptor) / sizeof(descriptor[0]), descriptor, &env);
        JSVM_EnvScope envScope;
        OH_JSVM_OpenEnvScope(env, &envScope);
        // open handle scope
        JSVM_HandleScope handleScope;
        OH_JSVM_OpenHandleScope(env, &handleScope);
        std::string sourceCodeStr = "\
{\
let value = add(4.96, 5.28);\
consoleinfo('Result is:' + value);\
}\
";
        // compile js script
        JSVM_Value sourceCodeValue;
        OH_JSVM_CreateStringUtf8(env, sourceCodeStr.c_str(), sourceCodeStr.size(), &sourceCodeValue);
        JSVM_Script script;
        OH_JSVM_CompileScript(env, sourceCodeValue, nullptr, 0, true, nullptr, &script);
        JSVM_Value result;
        // run js script
        OH_JSVM_RunScript(env, script, &result);
        JSVM_ValueType type;
        OH_JSVM_Typeof(env, result, &type);
        OH_LOG_INFO(LOG_APP, "JSVM API TEST type: %{public}d", type);
        // exit vm and clean memory
        OH_JSVM_CloseHandleScope(env, handleScope);
        OH_JSVM_CloseEnvScope(env, envScope);
        OH_JSVM_DestroyEnv(env);
        OH_JSVM_CloseVMScope(vm, vmScope);
        OH_JSVM_DestroyVM(vm);
    });

    t.detach();

    return nullptr;
}

使用 JSVM-API 接口编译及执行 JS 代码

场景介绍

编译及执行JS代码。

接口说明

接口 功能说明
OH_JSVM_CompileScript 编译JavaScript代码并返回绑定到当前环境的编译脚本
OH_JSVM_CompileScriptWithOrigin 编译JavaScript代码并返回绑定到当前环境的编译脚本,同时传入包括 sourceMapUrl 和源文件名在内的源代码信息,用于处理 source map 信息
OH_JSVM_CreateCodeCache 为编译脚本创建code cache
OH_JSVM_RunScript 执行编译脚本

场景示例

编译及执行JS代码(创建vm,注册function,执行js,销毁vm)。

#include <cstring>
#include <fstream>
#include <string>
#include <vector>

// 依赖libjsvm.so
#include "ark_runtime/jsvm.h"

using namespace std;

static JSVM_Value Hello(JSVM_Env env, JSVM_CallbackInfo info) {
    JSVM_Value output;
    void* data = nullptr;
    OH_JSVM_GetCbInfo(env, info, nullptr, nullptr, nullptr, &data);
    OH_JSVM_CreateStringUtf8(env, (char*)data, strlen((char*)data), &output);
    return output;
}

static JSVM_CallbackStruct hello_cb = { Hello, (void*)"Hello" };

static string srcGlobal = R"JS(
const concat = (...args) => args.reduce((a, b) => a + b);
throw new Error("exception triggered")
)JS";

static void RunScript(JSVM_Env env, string& src,
                       bool withOrigin = false,
                       const uint8_t** dataPtr = nullptr,
                       size_t* lengthPtr = nullptr) {
    JSVM_HandleScope handleScope;
    OH_JSVM_OpenHandleScope(env, &handleScope);

    JSVM_Value jsSrc;
    OH_JSVM_CreateStringUtf8(env, src.c_str(), src.size(), &jsSrc);

    const uint8_t* data = dataPtr ? *dataPtr : n
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值