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