场景介绍
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模块之间的交互流程,主要分为以下两步:
- 初始化阶段:在Native模块上初始化JSVM和JS上下文,并完成Native函数的注册。Native方法将会被挂载到JS执行环境的全局上下文即GlobalThis。
- 调用阶段:当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, ¶m[0], NULL, NULL, NULL, JSVM_DEFAULT},
{"add", NULL, ¶m[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