** QuickJS ** is a lightweight JavaScript engine that can be embedded in C / C ++. The license is MIT license. It seems to be useful when you want to embed JavaScript but V8 is too over-engineered.
Click here for the official page, which is too simple in design and has a sense of trust.
QuickJS is a small and embeddable Javascript engine. It supports the ES2020 specification including modules, asynchronous generators, proxies and BigInt. QuickJS is a small, embeddable JavaScript engine that supports the ES2020 specification, including modules, asynchronous generators, proxies, and BigInt.
By the way, QuickJS author Fabrice Bellard is also the author of qemu and ffmpeg. Incarnation of behavioral power ... (Image omitted)
There is also an unofficial mirror on GitHub. This is linked from the official page as “Unofficial git mirror”. However, as of the writing of the article, the latest release (2020-01-05) is not reflected and it remains the 2019-10-27 version.
This article outlines how to incorporate QuickJS into C / C ++. It's like my memorandum, not systematic and exhaustive, so please understand in advance.
You can install it by unpacking the tarball and doing make install
as usual. By default, it is installed under / usr / local
, but you can change it by specifying prefix
. The following is written on the assumption that it will be installed in ~ / .local
, so read it as appropriate.
#Download source
curl -LO https://bellard.org/quickjs/quickjs-2020-01-05.tar.xz
#Unpack the tarball
tar axvf quickjs-2020-01-05.tar.xz
#Build~/.Example of installing under local
# -j (--jobs)Is the number of parallel executions, so adjust accordingly
make -C quickjs-2020-01-05 -j 2 prefix="${HOME}/.local" install
If you want to use the qjs
command, pass the PATH
environment variable appropriately.
PATH="${HOME}/.local/bin:${PATH}"
export PATH
It can also be installed via AUR for Arch Linux users and Homebrew for macOS users.
qjs
/ qjsc
commandCalling the qjs
command with no arguments launches the REPL.
$ qjs
QuickJS - Type "\h" for help
qjs > \h
\h this help
\x hexadecimal number display
\d *decimal number display
\t toggle timing display
\clear clear the terminal
\q exit
qjs > 3**2 + 4**2
25
qjs > 2n ** 256n
115792089237316195423570985008687907853269984665640564039457584007913129639936n
qjs > const name = "world"
undefined
qjs > `Hello, ${name}`
"Hello, world"
qjs > /^(a)(b*)(c+)(d?)$/.exec("abbbcc")
[ "abbbcc", "a", "bbb", "cc", "" ]
qjs >
If you give a file name to the qjs
command, that file will be executed. You can also ʻimport / ʻexport
.
greeter.js
export function greet(name) {
console.log(`Hello, ${name}!`);
}
index.js
import { greet } from "./greeter.js";
greet("Alice");
$ qjs index.js
Hello, Alice!
You can make JavaScript an executable file using the qjsc
command.
$ qjsc index.js
$ strip a.out
$ file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=315625503ecf284b44cab3b6f1d3dea6df4dedc7, for GNU/Linux 3.2.0, stripped
$ stat -c '%s' a.out | numfmt --to=iec-i
832Ki
$ ./a.out
Hello, Alice!
The main subject is from here.
The official documentation for the C API has only a few dozen lines of sections:
Well, from the header (quickjs.h
), you can see the behavior, and it works as it is. After that, if you read the REPL source (qjs.c
) and the implementation of the library itself (quickjs.c
), you can understand how to use it.
The entire source code illustrated this time is placed in the following repository.
To get started, let's call the foo
function defined in JS using the C API. The code is as follows (By the way, this corresponds to the code example of Lua (programming language) --C API of Wikipedia (en). ing).
simple.c
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <quickjs.h>
int main(void) {
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
char *const fooCode = "function foo(x, y) { return x + y; }";
if (JS_IsException(JS_Eval(ctx, fooCode, strlen(fooCode), "<input>", JS_EVAL_FLAG_STRICT))) {
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return -1;
}
JSValue global = JS_GetGlobalObject(ctx);
JSValue foo = JS_GetPropertyStr(ctx, global, "foo");
JSValue argv[] = { JS_NewInt32(ctx, 5), JS_NewInt32(ctx, 3) };
JSValue jsResult = JS_Call(ctx, foo, global, sizeof(argv) / sizeof(JSValue), argv);
int32_t result;
JS_ToInt32(ctx, &result, jsResult);
printf("Result: %d\n", result);
JSValue used[] = { jsResult, argv[1], argv[0], foo, global };
for (int i = 0; i < sizeof(used) / sizeof(JSValue); ++i) {
JS_FreeValue(ctx, used[i]);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
Build the above source with the following command.
#compile(-Optimized for size with Os)
gcc -c -Os -Wall -I"${HOME}/.local/include" simple.c
#Link(-Wl,-strip with s)
gcc -Wl,-s -L"${HOME}/.local/lib/quickjs" simple.o -l quickjs -l m -o simple
When executed, the result of 5 + 3
is displayed.
$ ./simple
Result: 8
This code is processed in the following flow.
JS_NewRuntime
JS_NewContext
JS_Eval
JSValue JS_Eval (JSContext * ctx, const char * input, size_t input_len, const char * filename, int eval_flags);
signatureJS_GetGlobalObject
JS_GetPropertyStr
JS_NewInt32
JS_Call
JSValue JS_Call (JSContext * ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst * argv);
JS_ToInt32
JS_FreeValue
to free JS objects (reduce reference counters) JS_FreeRuntime: Assertion `list_empty (& rt-> gc_obj_list)' failed.
JS_FreeValue
because what you generate with JS_NewInt32
is a value type, not a reference type, but it's best to call it for consistency.true
, ʻundefined,
nullwith
JS_FreeValue`.JS_DupValue
JS_FreeValue
Somehow I can see the law that distinguishes between those who must / must not, but I do not fully understand it, so I am doing it in an atmosphere (please point out)Get
guy ʻEval needs
Free, and the
New` guy needs to be thrown away immediately afterwards </ del> JSValue
, which has ownership here, is always Free
. When you want to give up ownership to the JS side, Dup
(offset Free
) and give it." Maybe it looks good (useful if you want to Free
with a C ++ destructor)JS_FreeContext
JS_FreeRuntime
An example of creating an application that executes the JavaScript given as a command line argument and displays the result on standard output. The operation image is as follows.
$ ./jseval '3**2 + 4**2'
25
$ ./jseval foo
ReferenceError: foo is not defined
$ ./jseval 'undefined'
$ ./jseval '[3, 4, 5].map(x => x ** 10).forEach(x => console.log(x))'
59049
1048576
9765625
The code is as follows. console.log
/ console.error
is implemented in C so that it can be used from JS.
jseval.c
#include <stdio.h>
#include <string.h>
#include <quickjs.h>
JSValue jsFprint(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv, FILE *f) {
for (int i = 0; i < argc; ++i) {
if (i != 0) {
fputc(' ', f);
}
const char *str = JS_ToCString(ctx, argv[i]);
if (!str) {
return JS_EXCEPTION;
}
fputs(str, f);
JS_FreeCString(ctx, str);
}
fputc('\n', f);
return JS_UNDEFINED;
}
JSValue jsPrint(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
return jsFprint(ctx, jsThis, argc, argv, stdout);
}
JSValue jsPrintErr(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
return jsFprint(ctx, jsThis, argc, argv, stderr);
}
void initContext(JSContext *ctx) {
JSValue global = JS_GetGlobalObject(ctx);
//Add console to globalThis
JSValue console = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, global, "console", console);
// console.set log
JS_SetPropertyStr(ctx, console, "log", JS_NewCFunction(ctx, jsPrint, "log", 1));
// console.Set error
JS_SetPropertyStr(ctx, console, "error", JS_NewCFunction(ctx, jsPrintErr, "error", 1));
JS_FreeValue(ctx, global);
}
int main(int argc, char const *argv[]) {
int exitCode = 0;
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
initContext(ctx);
for (int i = 1; i < argc; ++i) {
JSValue ret = JS_Eval(ctx, argv[i], strlen(argv[i]), "<input>", JS_EVAL_FLAG_STRICT);
if (JS_IsException(ret)) {
JSValue e = JS_GetException(ctx);
jsPrintErr(ctx, JS_NULL, 1, &e);
JS_FreeValue(ctx, e);
exitCode = 1;
break;
} else if (JS_IsUndefined(ret)) {
// nop
} else {
jsPrint(ctx, JS_NULL, 1, &ret);
}
JS_FreeValue(ctx, ret);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return exitCode;
}
JS_SetPropertyStr
JS_NewCFunction
to treat C functions as JS functionsJSValue JS_NewCFunction (JSContext * ctx, JSCFunction * func, const char * name, int length)
JSCFunction
istypedef JSValue JSCFunction (JSContext * ctx, JSValueConst this_val, int argc, JSValueConst * argv);
this
and its arguments (number and pointer to the beginning of the array) and returns a valueJSValueConst
is" JSValuedoes not have to be
JS_FreeValue`"JS_IsException (return value of JS_Eval)
is truthy, JS_GetException
gets and displays the object of the exception that occurred in the context.From here, the example code suddenly becomes C ++ instead of C. I've had a lot of trouble ...
For example, you want to use C ++ std :: mt19937
(pseudo-random number generator) on the JavaScript side as follows. Think.
const mt = new Mt19937();
for (let i = 0; i < 10; ++i) {
console.log(mt.generate()); //random number(BigInt)To output
}
output
3499211612
581869302
3890346734
3586334585
545404204
4161255391
3922919429
949333985
2715962298
1323567403
This can be written as:
mt19937.cc
//Unique ID of Mt19937 class (initialized later)
//It is defined globally for the sake of simplicity, but it will break when multiple Runtimes are run at the same time, so it is necessary to do it appropriately.
static JSClassID jsMt19937ClassID;
// Mt19937.prototype.generate
JSValue jsMt19937Generate(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
std::mt19937 *p = static_cast<std::mt19937 *>(JS_GetOpaque(jsThis, jsMt19937ClassID));
return JS_NewBigUint64(ctx, (*p)());
}
// Mt19937.prototype
const JSCFunctionListEntry jsMt19937ProtoFuncs[] = {
JS_CFUNC_DEF("generate", 1, jsMt19937Generate),
};
//Mt19937 constructor
JSValue jsMt19937New(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv) {
//Create an instance
JSValue obj = JS_NewObjectClass(ctx, jsMt19937ClassID);
bool fail = false;
if (argc == 0) {
JS_SetOpaque(obj, new std::mt19937());
} else if (argc == 1) {
// ... (If you want to set a seed value. Omitted because it is not essential)
} else {
fail = true;
}
if (fail) {
JS_FreeValue(ctx, obj); //Tend to forget
return JS_EXCEPTION;
}
return obj;
}
//Called when the Mt19937 object is retrieved by the GC
void jsMt19937Finalizer(JSRuntime *rt, JSValue val) {
std::mt19937 *p = static_cast<std::mt19937 *>(JS_GetOpaque(val, jsMt19937ClassID));
delete p;
}
//Definition of Mt19937 class
//When there is a dependency between objects that are not exposed on the JS side.gc_It seems that mark also needs to be collected
JSClassDef jsMt19937Class = {
"Mt19937",
.finalizer = jsMt19937Finalizer,
};
void initContext(JSContext *ctx) {
// ...
//Initialize Mt19937 class ID
JS_NewClassID(&jsMt19937ClassID);
//Register class at runtime
JS_NewClass(JS_GetRuntime(ctx), jsMt19937ClassID, &jsMt19937Class);
//prototype setting
JSValue mt19937Proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, mt19937Proto, jsMt19937ProtoFuncs, std::extent_v<decltype(jsMt19937ProtoFuncs)>);
JS_SetClassProto(ctx, jsMt19937ClassID, mt19937Proto);
//Added Mt19937 to globalThis
JS_SetPropertyStr(ctx, global, "Mt19937", JS_NewCFunction2(ctx, jsMt19937New, "Mt19937", 1, JS_CFUNC_constructor, 0));
// ...
}
The liver is JS_NewClass
/ JS_NewObjectClass
and JS_GetOpaque
/ JS_SetOpaque
. In this way, the GC can take care of the lifetime of std :: mt19937 *
.
If you want to do ʻimport / ʻexport
of a module, you need the JS_EVAL_TYPE_MODULE
flag at JS_Eval
. If this flag is in effect, the return value ret
will be either JS_UNDEFINED
or JS_EXCEPTION
.
JSValue ret = JS_Eval(ctx, argv[i], strlen(argv[i]), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_STRICT);
If you want to load a JS file as a module from the filesystem like the qjs
command, you need to register a function like js_module_loader
in quickjs-libc.c
with JS_SetModuleLoaderFunc
.
Consider the case where you want to make Mt19937
in the previous section ʻimport from the
rand` module instead of giving it as a global property.
import { Mt19937 } from "rand";
This can be written as:
rand.cc
//List of functions in rand module
static const JSCFunctionListEntry randFuncs[] = {
JS_CFUNC_SPECIAL_DEF("Mt19937", 1, constructor, jsMt19937New), // new Mt19937()It can be so
// JS_CFUNC_DEF("Mt19937", 1, jsMt19937New), // Mt19937()If you want to
};
//Initialization of rand module (the one called when importing JS)
int initRand(JSContext *ctx, JSModuleDef *m) {
JS_NewClassID(&jsMt19937ClassID);
JS_NewClass(JS_GetRuntime(ctx), jsMt19937ClassID, &jsMt19937Class);
//prototype setting
JSValue mt19937Proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, mt19937Proto, jsMt19937ProtoFuncs, std::extent_v<decltype(jsMt19937ProtoFuncs)>);
JS_SetClassProto(ctx, jsMt19937ClassID, mt19937Proto);
//The last argument is sizeof(randFuncs) / sizeof(JSCFunctionListEntry)Meaning
return JS_SetModuleExportList(ctx, m, randFuncs, std::extent_v<decltype(randFuncs)>);
}
//Definition of rand module
JSModuleDef *initRandModule(JSContext *ctx, const char *moduleName) {
JSModuleDef *m = JS_NewCModule(ctx, moduleName, initRand);
if (!m) {
return nullptr;
}
JS_AddModuleExportList(ctx, m, randFuncs, std::extent_v<decltype(randFuncs)>);
return m;
}
void initContext(JSContext *ctx) {
// ...
initRandModule(ctx, "rand");
// ...
}
int main(int argc, char const *argv[]) {
// ...
// JS_EVAL_TYPE_Only MODULE can be imported
JSValue ret = JS_Eval(ctx, argv[i], strlen(argv[i]), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_STRICT);
// ...
}
You can register the module in the context by creating JSCFunctionListEntry []
and doing JS_NewCModule
.
qjs
If you name the module initialization function js_init_module
and compile it as a shared library, you can ʻimport from the script executed by the
qjscommand. This feature is implemented in the
js_module_loader_so function of
quickjs-libc.c`.
Usage image
import { Mt19937 } from "./librand.so";
rand.cc
//Definition of rand module
extern "C" JSModuleDef *js_init_module(JSContext *ctx, const char *moduleName) {
JSModuleDef *m = JS_NewCModule(ctx, moduleName, initRand);
if (!m) {
return nullptr;
}
JS_AddModuleExportList(ctx, m, randFuncs, std::extent_v<decltype(randFuncs)>);
return m;
}
g++ -c -fPIC -Os -std=c++17 -Wall -I"${HOME}/.local/include/quickjs" rand.cc
#on macOS-undefined dynamic_Also add lookup
g++ -shared -Wl,-s -L"${HOME}/.local/lib/quickjs" rand.o -o librand.so
QuickJS is amazing because the complete JavaScript processing system is completed with only a few C source files (impression). However, there are thousands to tens of thousands of lines per file. The contents seem to be relatively easy to read, so I would like to try code reading as well.
Other lightweight languages that can be incorporated into C / C ++ are Lua, mruby, Squirrel -lang.org/) etc., but I feel that using JavaScript (QuickJS) instead is an option. For example, if you can write application configuration files and game scripts in JS, you can use eslint + prettier, or you can convert from TypeScript / JSX with tsc or babel, so it may be useful and interesting (appropriate).
After that, I need a person who can easily bind C / C ++ ⇆ JavaScript for practical use. Layers equivalent to tolua ++ or luabind in Lua (I haven't used it, but now Is sol the mainstream?). Personally, I want a Rust macro that does a good job. Currently, there is a wrapper called quick-js
, but it lacks some functionality, so I'd like to implement it myself or not.
Recommended Posts