quickjs是一个C++实现的轻量级javascript解析引擎,可以嵌入到C++程序中,实现C++和js代码的交互。
以下基于quickjs-ng这一社区分支实现样例代码演示利用quickjs编写程序进行C++和js互相调用,支持linux和windows。
代码结构
quickjs_demo
- quickjs-0.5.0
- main.cpp # C++主执行程序
- main.js # js执行程序
- sample.hpp # C++模块代码,供js调用
- sample.js # js模块代码,供C++调用
- CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(quickjs_demo)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (WIN32)
add_definitions(
-D_CRT_SECURE_NO_WARNINGS
-D_WINSOCK_DEPRECATED_NO_WARNINGS
)
elseif (UNIX)
add_compile_options(
-fPIC
-O3
)
endif()
add_subdirectory(./quickjs-0.5.0)
include_directories(./quickjs-0.5.0)
# build host executable
file(GLOB SRC
main.cpp
)
add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
qjs
pthread
)
基本原理为
- C++调用js:在C++中启动js运行时,加载js代码执行,可以返回js执行结果在C++中继续处理
- js调用C++:仍然在C++中启动js运行时,将C++定义的代码模块注册,加载js代码执行,调用注册好的C++模块,返回的结果可以在js中继续处理
基于这样的机制,就可以做到在C++的程序框架中C++与js双向交互,实现很多纯C++或者纯js达不到的效果,例如代码热更新以及安全隔离,这种机制目前其实在金融数据分析系统和游戏引擎中广泛使用。
C++调用js
sample.js
const a = 3;
const b = 5;
function my_func(x, y, text)
{
// the input params type, x is int, y is double, text is string, return z is double
// console.log("my_func with params:", x, y, text);
let z = x * y + (b - a);
return z;
}
C++代码
void cpp_call_js_test()
{
std::cout << "--- cpp call js test ---" << std::endl;
// init js runtime and context
JSRuntime* rt = JS_NewRuntime();
JSContext* ctx = JS_NewContext(rt);
// define global js object
JSValue global_obj = JS_GetGlobalObject(ctx);
// load js script
std::string js_file = "./sample.js";
std::ifstream in(js_file.c_str());
std::ostringstream sin;
sin << in.rdbuf();
std::string script_text = sin.str();
std::cout << "script text: " << std::endl;
std::cout << script_text << std::endl;
// run script
std::cout << "script run: " << std::endl;
JSValue script = JS_Eval(ctx, script_text.c_str(), script_text.length(), "sample", JS_EVAL_TYPE_GLOBAL);
if (!JS_IsException(script))
{
int x = 7;
double y = 8.9;
std::string text = "called from cpp";
JSValue js_x = JS_NewInt32(ctx, x);
JSValue js_y = JS_NewFloat64(ctx, y);
JSValue js_text = JS_NewString(ctx, text.c_str());
JSValue js_result;
JSValue my_func = JS_GetPropertyStr(ctx, global_obj, "my_func");
if (JS_IsFunction(ctx, my_func))
{
JSValue params[] = {js_x, js_y, js_text};
// call js function
js_result = JS_Call(ctx, my_func, JS_UNDEFINED, 3, params);
if (!JS_IsException(js_result))
{
double result = 0.0;
JS_ToFloat64(ctx, &result, js_result);
std::cout << "my_func result: " << result << std::endl;
}
else
std::cerr << "JS_Call failed" << std::endl;
}
JS_FreeValue(ctx, my_func);
JS_FreeValue(ctx, js_result);
JS_FreeValue(ctx, js_text);
JS_FreeValue(ctx, js_y);
JS_FreeValue(ctx, js_x);
}
else
std::cerr << "JS_Eval failed" << std::endl;
// close js runtime and context
JS_FreeValue(ctx, global_obj);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
js调用C++
sample.hpp
#include <iostream>
#include "quickjs.h"
#define JS_INIT_MODULE js_init_module
#define countof(x) (sizeof(x) / sizeof((x)[0]))
// define native variable and function
const int a = 3;
const int b = 5;
static double my_func(int x, double y, const char* text)
{
std::cout << "my_func with params: " << x << ", " << y << ", " << text << std::endl;
double z = x * y + (b - a);
return z;
}
// define quickjs C function
static JSValue js_my_func(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
std::cout << "js_my_func, argc: " << argc << std::endl;
if (argc != 3)
return JS_EXCEPTION;
int a = 0;
double b = 0.0;
if (JS_ToInt32(ctx, &a, argv[0]))
return JS_EXCEPTION;
if (JS_ToFloat64(ctx, &b, argv[1]))
return JS_EXCEPTION;
if (!JS_IsString(argv[2]))
return JS_EXCEPTION;
const char* text = JS_ToCString(ctx, argv[2]);
double z = my_func(a, b, text);
std::cout << "a: " << a << ", b: " << b << ", text: " << text << ", z: " << z << std::endl;
return JS_NewFloat64(ctx, z);
}
// define function entry list
static const JSCFunctionListEntry js_my_funcs[] =
{
JS_CFUNC_DEF("my_func", 3, js_my_func),
};
static int js_my_init(JSContext *ctx, JSModuleDef *m)
{
return JS_SetModuleExportList(ctx, m, js_my_funcs, countof(js_my_funcs));
}
JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
JSModuleDef *m;
m = JS_NewCModule(ctx, module_name, js_my_init);
if (!m)
return NULL;
JS_AddModuleExportList(ctx, m, js_my_funcs, countof(js_my_funcs));
return m;
}
main.js
let a = 7;
let b = 8.9;
let text = "called from js";
// call cpp function
let result = my_func(a, b, text);
// console.log("my_func result: ", result);
C++代码
void js_call_cpp_test()
{
std::cout << "--- js call cpp test ---" << std::endl;
// init js runtime and context
JSRuntime* rt = JS_NewRuntime();
JSContext* ctx = JS_NewContext(rt);
// define global js object
JSValue global_obj = JS_GetGlobalObject(ctx);
// register C++ function to current context
JSValue func_val = JS_NewCFunction(ctx, js_my_func, "my_func", 1);
if (JS_IsException(func_val))
std::cerr << "JS_NewCFunction failed" << std::endl;
if (JS_DefinePropertyValueStr(ctx, global_obj, "my_func", func_val, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0)
std::cerr << "JS_DefinePropertyValue failed" << std::endl;
std::string js_file = "./main.js";
std::ifstream in(js_file.c_str());
std::ostringstream sin;
sin << in.rdbuf();
std::string script_text = sin.str();
std::cout << "script text: " << std::endl;
std::cout << script_text << std::endl;
std::cout << "script run: " << std::endl;
JSValue script = JS_Eval(ctx, script_text.c_str(), script_text.length(), "main", JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(script))
std::cerr << "JS_Eval failed" << std::endl;
// close js runtime and context
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
主程序
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "quickjs.h"
#include "quickjs-libc.h"
#include "sample.hpp"
// void cpp_call_js_test();
// ...
// void js_call_cpp_test();
// ...
int main()
{
// test cpp call js script
cpp_call_js_test();
// test js call cpp module
js_call_cpp_test();
return 0;
}
执行结果
--- cpp call js test ---
script text:
const a = 3;
const b = 5;
function my_func(x, y, text)
{
// the input params type, x is int, y is double, text is string, return z is double
// console.log("my_func with params:", x, y, text);
let z = x * y + (b - a);
return z;
}
script run:
my_func result: 64.3
--- js call cpp test ---
script text:
let a = 7;
let b = 8.9;
let text = "called from js";
// call cpp function
let result = my_func(a, b, text);
// console.log("my_func result: ", result);
script run:
js_my_func, argc: 3
my_func with params: 7, 8.9, called from js
a: 7, b: 8.9, text: called from js, z: 64.3
记得要将sample.js和main.js拷贝到执行目录
备注:
- 由于quickjs的执行环境比较轻量级,在js代码里不能使用console.log等浏览器支持的内置函数,如果要打印日志,可以在C++中封装函数模块给js调用
- 需要在支持C++20的编译器下使用,如果编译不过,建议升级gcc或msvc
源码
quickjs_demo