场景描述
通过JSVM,可以在应用运行期间直接执行一段动态加载的JS代码。也可以选择将一些对性能、底层系统调用有较高要求的核心功能用C/C++实现并将C++方法注册到JS侧,在JS代码中直接调用,提高应用的执行效率。
功能描述
通过createJsCore方法来创建一个新的JS基础运行时环境,并通过该方法获得一个虚拟机ID,通过evalUateJS方法使用虚拟机ID对应的运行环境来运行JS代码,在JS代码中创建promise并异步调取TS侧设定的callback函数,最后使用releaseJsCore方法来释放虚拟机ID对应的运行环境。
JSVM主要交互流程

接口介绍
创建所需接口:
执行所需接口:
关闭所需接口:
场景代码
场景一:JSVM执行JS代码并回调C++代码
创建虚拟机所需环境,编译并执行JS代码回调C++代码,销毁之前创建的虚拟机环境。
配置
ArkTS代码:
示例中包含了提供给JSVM的JS里的consoleinfo同步方法,createPromise创建异步方法,执行assertEqual方法,onJSResultCallback异步方法回调。
1. 调用Native的创建虚拟机及环境的接口,绑定MyCallback回调(用于JS代码里onJSResultCallback方法的回调),接收native返回的虚拟机id。
2. 调用Native的执行JS代码的接口,传入虚拟机id和JS代码字符串,在native侧的虚拟机里编译并执行。
3. 调用Native的销毁虚拟机及环境的接口,传入虚拟机id,指定销毁对应的虚拟机和环境。
可以同时运行多个JSVM虚拟机执行多个JS代码。
Native代码:
JS运行环境创建,创建让JS代码运行的虚拟机环境:
1. 通过OH_JSVM_Init初始化虚拟机。
2. 通过OH_JSVM_CreateVM创建虚拟机实例。
3. 通过OH_JSVM_OpenVMScope创建虚拟机作用域。
4. 将本地函数的回调函数放到JSVM_PropertyDescriptor集合中(用于JS调用的C++函数)。
5. 通过OH_JSVM_CreateEnv创建上下文环境并注册JSVM_PropertyDescriptor。
6. 通过OH_JSVM_OpenEnvScope创建上下文环境作用域。
static void CreateArkJSContext() {
JSVM_Status status;
JSVM_InitOptions init_options;
memset(&init_options, 0, sizeof(init_options));
if (aa == 0) {
//****1****初始化JSVM
OH_JSVM_Init(&init_options);
aa++;
}
g_vmMap[ENVTAG_NUMBER] = new JSVM_VM;
JSVM_VMScope vmScope;
g_vmScopeMap[ENVTAG_NUMBER] = vmScope;
JSVM_CreateVMOptions options;
memset(&options, 0, sizeof(options));
//****2****创建JSVM实例
status = OH_JSVM_CreateVM(&options, g_vmMap[ENVTAG_NUMBER]);
//****3****创建JSVM作用域
status = OH_JSVM_OpenVMScope(*g_vmMap[ENVTAG_NUMBER], &g_vmScopeMap[ENVTAG_NUMBER]);
g_envMap[ENVTAG_NUMBER] = new JSVM_Env;
g_callBackStructMap[ENVTAG_NUMBER] = new JSVM_CallbackStruct[5];
// 注册用户提供的本地函数的回调函数指针和数据,通过JSVM-API暴露给JS
for (int i = 0; i < 5; i++) {
g_callBackStructMap[ENVTAG_NUMBER][i].data = nullptr;
}
g_callBackStructMap[ENVTAG_NUMBER][0].callback = Consoleinfo;
g_callBackStructMap[ENVTAG_NUMBER][1].callback = Add;
g_callBackStructMap[ENVTAG_NUMBER][2].callback = AssertEqual;
g_callBackStructMap[ENVTAG_NUMBER][3].callback = OnJSResultCallback;
g_callBackStructMap[ENVTAG_NUMBER][4].callback = CreatePromise;
//****4****将本地函数的回调函数放到JSVM_PropertyDescriptor集合中
JSVM_PropertyDescriptor descriptors[] = {
{"consoleinfo", NULL, &g_callBackStructMap[ENVTAG_NUMBER][0], NULL, NULL, NULL, JSVM_DEFAULT},
{"add", NULL, &g_callBackStructMap[ENVTAG_NUMBER][1], NULL, NULL, NULL, JSVM_DEFAULT},
{"assertEqual", NULL, &g_callBackStructMap[ENVTAG_NUMBER][2], NULL, NULL, NULL, JSVM_DEFAULT},
{"onJSResultCallback", NULL, &g_callBackStructMap[ENVTAG_NUMBER][3], NULL, NULL, NULL, JSVM_DEFAULT},
{"createPromise", NULL, &g_callBackStructMap[ENVTAG_NUMBER][4], NULL, NULL, NULL, JSVM_DEFAULT},
};
//****5****创建JSVM环境
status = OH_JSVM_CreateEnv(*g_vmMap[ENVTAG_NUMBER], sizeof(descriptors) / sizeof(descriptors[0]), descriptors, g_envMap[ENVTAG_NUMBER]);
JSVM_EnvScope envScope;
g_envScopeMap[ENVTAG_NUMBER] = envScope;
//****6****创建JSVM环境作用域
status = OH_JSVM_OpenEnvScope(*g_envMap[ENVTAG_NUMBER], &g_envScopeMap[ENVTAG_NUMBER]);
}
// 提供创建JSVM运行环境的对外接口并返回对应唯一ID
static napi_value CreateJsCore(napi_env env1, napi_callback_info info) {
OH_LOG_ERROR(LOG_APP, "JSVM CreateJsCore START");
size_t argc = 1;
napi_value argv[1];
napi_get_cb_info(env1, info, &argc, argv, nullptr, nullptr);
if (argc < 1) {
OH_LOG_ERROR(LOG_APP, "JSVM CreateJsCore the number of params must be one");
return nullptr;
}
g_napiEnvMap[ENVTAG_NUMBER] = env1;
g_taskQueueMap[ENVTAG_NUMBER] = deque<Task *>{};
// 将TS侧传入的回调函数与env对应存储方便后续调用
//napi_ref生命周期由开发者自己管理,需要手动delete,引用 napi_value 的抽象。这允许用户管理 JavaScript 值的生命周期,包括明确定义它们的最小生命周期。
napi_ref callFun;
//为Object创建一个reference,以延长其生命周期。调用者需要自己管理reference生命周期。
napi_create_reference(env1, argv[0], 1, &callFun);
g_callBackMap[ENVTAG_NUMBER] = callFun;
napi_value coreID = 0;
{
//互斥锁
std::lock_guard<std::mutex> lock_guard(envMapLock);
CreateArkJSContext();
//累加
napi_create_uint32(env1, ENVTAG_NUMBER, &coreID);
ENVTAG_NUMBER++;
}
OH_LOG_ERROR(LOG_APP, "JSVM CreateJsCore END");
return coreID;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
JS代码执行,在虚拟机环境中编译并运行JS代码:
1. 通过OH_JSVM_OpenHandleScope开启新的作用域。
2. 通过OH_JSVM_CompileScript对传入的JS代码进行编译。
3. 通过OH_JSVM_RunScript运行JS代码。
4. 通过OH_JSVM_CloseHandleScope关闭作用域。
static std::mutex mutexLock;
// 对外提供执行JS代码接口,通过coreID在对应的JSVN环境中执行JS代码
static napi_value EvalUateJS(napi_env env, napi_callback_info info) {
OH_LOG_ERROR(LOG_APP, "JSVM EvalUateJS START");
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
uint32_t envId;
napi_status status = napi_get_value_uint32(env, args[0], &envId);
if (status != napi_ok) {
OH_LOG_ERROR(LOG_APP, "EvalUateJS first param should be number");
return nullptr;
}
if (g_envMap.count(envId) == 0 || g_envMap[envId] == nullptr) {
OH_LOG_ERROR(LOG_APP, "EvalUateJS env is null");
return nullptr;
}
//获取传入的JS代码,转char[]类型
std::string dataStr = napiValueToString(env, args[1]);
napi_value res = nullptr;
std::lock_guard<std::mutex> lock_guard(mutexLock);
{
// open handle scope
JSVM_HandleScope handlescope;
//****1**** 开启新作用域
OH_JSVM_OpenHandleScope(*g_envMap[envId], &handlescope);
// compile js
JSVM_Value sourcecodevalue;
// 创建一个由utf-8转的JS字符串
OH_JSVM_CreateStringUtf8(*g_envMap[envId], dataStr.c_str(), dataStr.size(), &sourcecodevalue);
JSVM_Script script;
//****2****编译一串JS代码,并返回编译后脚本
OH_JSVM_CompileScript(*g_envMap[envId], sourcecodevalue, nullptr, 0, true, nullptr, &script);
// run JS
JSVM_Value result;
//****3****执行一段JS代码并返回结果
OH_JSVM_RunScript(*g_envMap[envId], script, &result);
JSVM_ValueType type;
//调用typeof运算符的行为,得到结果类型
OH_JSVM_Typeof(*g_envMap[envId], result, &type);
OH_LOG_INFO(LOG_APP, "JSVM API TEST type: %{public}d", type);
// Execute tasks in the current env event queue
//执行并删除任务队列中的任务
while (!g_taskQueueMap[envId].empty()) {
auto task = g_taskQueueMap[envId].front();
g_taskQueueMap[envId].pop_front();
task->Run();
delete task;
}
if (type == JSVM_STRING) {
//JSVM_Value转字符串
std::string stdResult = fromOHStringValue(*g_envMap[envId], result);
napi_create_string_utf8(env, stdResult.c_str(), stdResult.length(), &res);
} else if (type == JSVM_BOOLEAN) {
bool ret = false;
std::string stdResult;
OH_JSVM_GetValueBool(*g_envMap[envId], result, &ret);
ret ? stdResult = "true" : stdResult = "false";
napi_create_string_utf8(env, stdResult.c_str(), stdResult.length(), &res);
} else if (type == JSVM_NUMBER) {
int32_t num;
OH_JSVM_GetValueInt32(*g_envMap[envId], result, &num);
std::string stdResult = std::to_string(num);
napi_create_string_utf8(env, stdResult.c_str(), stdResult.length(), &res);
} else if (type == JSVM_OBJECT) {
JSVM_Value objResult;
OH_JSVM_JsonStringify(*g_envMap[envId], result, &objResult);
std::string stdResult = fromOHStringValue(*g_envMap[envId], objResult);
napi_create_string_utf8(env, stdResult.c_str(), stdResult.length(), &res);
}
//****4****关闭传入的作用域
OH_JSVM_CloseHandleScope(*g_envMap[envId], handlescope);
}
OH_LOG_ERROR(LOG_APP, "JSVM EvalUateJS END");
return res;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
JS运行环境销毁
关闭和销毁虚拟机和上下文环境的作用域与实例。
1. 使用OH_JSVM_CloseEnvScope关闭上下文环境作用域。
2. 使用OH_JSVM_DestroyEnv销毁上下文环境。
3. 使用OH_JSVM_CloseVMScope关闭虚拟机作用域。
4. 使用OH_JSVM_DestroyVM销毁虚拟机实例。
JS中调用的C++代码实现
调用的C++方法类似于NAPI的写法,通过OH_JSVM_GetCbInfo获取JS传入方法的参数,将获取到的JSVM_Value的类型转换成c的类型。
自定义C++代码中的JSVM的常用接口与NAPI的接口类似,可参考文档底部的官方文档。
同步方法
用于打印日志的简单同步方法:
用于相加的简单同步方法:
异步方法
Promise方式的异步调用,主要调用OH_JSVM_CreatePromise创建一个延迟对象和一个JavaScript promise,OH_JSVM_ResolveDeferred通过与之关联的延迟对象来解析JavaScript promise,用于解析对应的可用的延迟对象的JavaScript Promise
可参考NAPI的异步任务:使用Node-API接口进行异步任务开发。
callback方式的异步调用,需要调用NAPI的异步调用方法。
// 用以在native层中调用TS侧传入的Callback函数
static JSVM_Value OnJSResultCallback(JSVM_Env env, JSVM_CallbackInfo info) {
size_t argc = 3;
JSVM_Value args[3];
JSVM_CALL(env, OH_JSVM_GetCbInfo(env, info, &argc, args, NULL, NULL));
int callId = 0;
OH_JSVM_GetValueInt32(env, args[0], &callId);
napi_value callArgs[2] = {nullptr, nullptr};
size_t size;
size_t size1;
OH_JSVM_GetValueStringUtf8(env, args[1], nullptr, 0, &size);
char Str1[size + 1];
OH_JSVM_GetValueStringUtf8(env, args[1], Str1, size + 1, &size);
OH_JSVM_GetValueStringUtf8(env, args[2], nullptr, 0, &size1);
char Str2[size1 + 1];
OH_JSVM_GetValueStringUtf8(env, args[2], Str2, size1 + 1, &size1);
napi_create_string_utf8(g_napiEnvMap[callId], Str1, size + 1, &callArgs[0]);
napi_create_string_utf8(g_napiEnvMap[callId], Str2, size1 + 1, &callArgs[1]);
napi_value callback = nullptr;
// 通过callId获取在创建当前JSVM环境时传入的TS回调方法
napi_get_reference_value(g_napiEnvMap[callId], g_callBackMap[callId], &callback);
napi_value ret;
// 执行TS回调方法
napi_call_function(g_napiEnvMap[callId], nullptr, callback, 2, callArgs, &ret);
char retStr[256];
napi_get_value_string_utf8(g_napiEnvMap[callId], ret, retStr, 256, &size);
JSVM_Value returnVal;
OH_JSVM_CreateStringUtf8(env, retStr, JSVM_AUTO_LENGTH, &returnVal);
return returnVal;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
OH_JSVM_StrictEquals提供类似调用严格相等算法的行为。
代码中使用的全局变量和工具方法,包含自定义的全局变量和工具方法。
#include "napi/native_api.h"
#include "ark_runtime/jsvm.h"
#include "common.h"
#include <bits/alltypes.h>
#include <deque>
#include <map>
#include <unistd.h>
#include <hilog/log.h>
#include <cstring>
#include <string>
#include <vector>
#include <sstream>
#define LOG_TAG "TEST_TAG"
using namespace std;
// 定义map管理每个独立vm环境
static map<int, JSVM_VM*> g_vmMap;
static map<int, JSVM_VMScope> g_vmScopeMap;
static map<int, JSVM_Env*> g_envMap;
static map<int, napi_env> g_napiEnvMap;
static map<int, JSVM_EnvScope> g_envScopeMap;
static map<int, napi_ref> g_callBackMap;
static map<int, JSVM_CallbackStruct*> g_callBackStructMap;
static uint32_t ENVTAG_NUMBER = 0;
static std::mutex envMapLock;
static int aa = 0;
class Task {
public:
//virtual虚析构函数在销毁时会调用对象的析构函数,这样就不会出现像有的数据成员没有销毁导致内存泄露的问题或者程序直接崩溃
virtual ~Task() = default;
// virtual纯虚函数的类是一个抽象类
virtual void Run() = 0;
};
//deque双端存储队列
static map<int, deque<Task *>> g_taskQueueMap;
std::string napiValueToString(napi_env env, napi_value nValue) {
size_t buffLen = 0;
napi_get_value_string_utf8(env, nValue, nullptr, 0, &buffLen);
char buffer[buffLen + 1];
napi_get_value_string_utf8(env, nValue, buffer, buffLen + 1, &buffLen);
return buffer;
}
static std::string fromOHStringValue(JSVM_Env &env, JSVM_Value &value) {
size_t size;
JSVM_Status status;
status = OH_JSVM_GetValueStringUtf8(env, value, nullptr, 0, &size);
char resultStr[size + 1];
status = OH_JSVM_GetValueStringUtf8(env, value, resultStr, size + 1, &size);
return resultStr;
}
// create_jsvm_runtime.cpp
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"createJsCore", nullptr, CreateJsCore, nullptr, nullptr, nullptr, napi_default, nullptr},
{"releaseJsCore", nullptr, ReleaseJsCore, nullptr, nullptr, nullptr, napi_default, nullptr},
{"evalUateJS", nullptr, EvalUateJS, 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 = "entry",
.nm_priv = ((void *)0),
.reserved = {0},
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
场景二:JSVM读取并执行文件的JS代码
一些场景需要去读取文件中的JS代码,可将JS代码放到/resources/rawfile/目录下,使用读取资源文件相关功能,将读取到的字符串,使用场景一的方式在JSVM执行JS代码,并回调C++代码。
从资源文件中读取JS代码
xxx.js文件放在/resources/rawfile/目录下
xxx.js
配置cmakelists:
ArkTS代码:
获取资源对象,调用native的方法传入资源对象和文件名称。
Native代码:
获取ArkTS传入的资源对象转成native对象,获取rawfile指针对象,获取rawfile大小并申请内存,然后读取文件内容,最后关闭指针,将获取到的内容去除\r。
JSVM执行JS代码
总体与场景一相同,只需修改JS代码,执行这段代码里获取ArkTS传入的JS字符串相关代码,将其替换为上面从JS文件中获取到的JS字符串。
官方文档
包含各种常用接口及介绍:JSVM。
资源文件读取可参考文档:Rawfile开发指导。