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 SRCmain.cpp
)add_executable(${PROJECT_NAME} ${SRC})target_link_libraries(${PROJECT_NAME} qjs
)
基本原理为
- 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 contextJSRuntime* rt = JS_NewRuntime();JSContext* ctx = JS_NewContext(rt);// define global js objectJSValue global_obj = JS_GetGlobalObject(ctx);// load js scriptstd::string js_file = "./sample.js";std::ifstream in(js_file); 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 scriptstd::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 functionjs_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;}elsestd::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);}elsestd::cerr << "JS_Eval failed" << std::endl;// close js runtime and contextJS_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 contextJSRuntime* rt = JS_NewRuntime();JSContext* ctx = JS_NewContext(rt);// define global js objectJSValue global_obj = JS_GetGlobalObject(ctx); // register C++ function to current contextJSValue 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); 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 contextJS_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 scriptcpp_call_js_test();// test js call cpp modulejs_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