QuickJS 快速入门 (QuickJS QuickStart)
1. quickjs 快速入门 (quickjs quickstart)
1.1. 简介
quickjs是一个小型的可嵌入javascript引擎。它支持es2020规范,包括模块、异步生成器和代理。它还支持数学扩展,比如大整数(bigint)、大浮点数(bigfloat)和操作符重载。
1.2. 安装
- linux 直接下载 源码
make && make install
- macos x 下 makefile 有 bug ,可以直接使用 homebrew 安装
brew install quickjs
- 执行
qjs
验证安装成功
1.3. 简单使用
1.3.1. 控制台执行
qjs
进入quickjs环境,-h
获取帮助,-q
退出环境。
直接执行js:
console.log(new date())
输出:wed aug 14 2019 23:51:43 gmt+0800
undefined
(function(){ return 1+1;})()
输出:2
1.3.2. js脚本执行
新建一个js脚本,名为hello.js
,内容为console.log('hello world !')
, 在js目录下执行
qjs hello.js
输出:hello world !
1.3.3. 编译二进制文件
将 quickjs.h
、quickjs-libc.h
、libquickjs.a
拷贝到js文件同目录下。
qjsc -o hello hello.js ls ./hello
输出:hello world !
编译出来的可执行文件的大小只有569k(2019-9-18版本为900k),没有任何外部依赖,非常适合嵌入式设备使用。
1.4. 全局对象
-
scriptargs
输入的命令行参数,第一个参数为脚本的名称。 -
print(...args)
、console.log(...args)
打印由空格和尾随换行符分隔的参数。
新建js脚本globle_obj.js
(function(){ if(typeof scriptargs != 'undefined'){ print(scriptargs); console.log(scriptargs[1]); } })()
qjs globle_obj.js -a 123 1234
输出:
globle_obj.js,-a,123,1234
-a
1.5. std 模块
std
模块为quickjs-libc
提供包装器stdlib.h
和stdio.h
和其他一些实用程序。
std代码示例:
创建文件std_m.js
import * as std from 'std'; var file = std.open('std_open_file.js','w'); file.puts('var file = std.open(\"std_open_file.txt\",\"w\");\n'); file.puts('file.puts(\'std_open_file line1\\n\');\n'); file.puts('file.puts(\'std_open_file line2\\n\');\n'); file.puts('file.close();\n'); file.close(); std.loadscript('std_open_file.js'); var rdfile = std.open("std_open_file.txt","r"); do{ console.log(rdfile.getline()); }while(!rdfile.eof()); rdfile.close();
执行qjs std_m.js
,目录下会生成2个新文件std_open_file.js
std_open_file.txt
。
控制台输出:std_open_file line1
std_open_file line2
null
1.6. os 模块
os
模块提供操作系统特定功能:底层文件访问、信号、计时器、异步 i/o。
代码示例:
import * as os from 'os'; os.remove('hello'); os.remove('std_open_file.js'); os.remove('std_open_file.txt');
删除生成的测试文件
1.7. 自定义c模块
es6模块完全支持。默认名称解析规则如下:
- 模块名称带有前导.或..是相对于当前模块的路径
- 模块名称没有前导.或..是系统模块,例如std或os
- 模块名称以.so结尾,是使用quickjs c api的原生模块
使用js文件模块和系统模块,参照引用原生js模块和上面的例子即可,这里就不多赘述。
这里着重讲解如何编写自己的原生c模块,并且以导入so文件的方式在js代码中使用。
1.7.1. js数据类型在c中的定义
typedef union jsvalueunion { int32_t int32; //整数值 double float64; //double值 void *ptr; //quickjs引用类型的指针 } jsvalueunion; //存放于同一地址,且互斥 typedef struct jsvalue { jsvalueunion u; //存放真实数值或着其指针 int64_t tag; //jsvalue类型的标示符(如 undefined 其 tag == js_tag_undefined) } jsvalue;
此结构定义在 quickjs.h 中。
1.7.2. c模块编写
流程如下:
- 自定义原生c函数
- 定义 quickjs c 函数
- 定义api的函数入口名称及列表
- 定义初始化回调方法,将函数入口列表在模块中暴露
- 定义初始化模块方法,由系统自动调用,且函数名称不可更改
创建编写c_test_m.c文件:
#include "quickjs.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #define js_init_module js_init_module #define countof(x) (sizeof(x) / sizeof((x)[0])) /* 自定义原生c函数 */ static double test_add(int a, double b) { return a + b; } static char *test_add_str(const char *a, double b) { /* 要有足够的空间来容纳要拼接的字符串,否则可能会造成缓冲溢出的错误情况 */ char instr[64]; sprintf(instr, "%.2f", b); char *dest = malloc(128); memset(dest, 0, 128); strcpy(dest, a); char *retdest = strcat(dest, instr); return dest; } /* 定义 quickjs c 函数 *ctx : 运行时上下文 this_val : this对象 argc : 入参个数 *argv : 入参列表 */ static jsvalue js_test_add(jscontext *ctx, jsvalueconst this_val, int argc, jsvalueconst *argv) { int a; double b; if (js_toint32(ctx, &a, argv[0])) return js_exception; if (js_tofloat64(ctx, &b, argv[1])) return js_exception; printf("argc = %d \n", argc); printf("a = %d \n", a); printf("b = %lf \n", b); printf("argv[1].u.float64 = %lf \n", argv[1].u.float64); return js_newfloat64(ctx, test_add(a, b)); } static jsvalue js_test_add_str(jscontext *ctx, jsvalueconst this_val, int argc, jsvalueconst *argv) { if (!js_isstring(argv[0])) { return js_exception; } double d; if (js_tofloat64(ctx, &d, argv[1])) return js_exception; const char *jscstr = js_tocstring(ctx, argv[0]); printf("js_tocstring(ctx, argv[0]) = %s \n", jscstr); printf("argv[1].u.float64 = %lf \n", argv[1].u.float64); char *jsret = test_add_str(jscstr, d); return js_newstring(ctx, jsret); } /* 定义api的函数入口名称及列表 */ static const jscfunctionlistentry js_test_funcs[] = { /* js_cfunc_def(函数入口名称,入参个数,quickjs c 函数) */ js_cfunc_def("testadd", 2, js_test_add), js_cfunc_def("testaddstr", 2, js_test_add_str), }; /* 定义初始化回调方法(由系统调用,入参格式固定),将函数入口列表 在模块中暴露 */ static int js_test_init(jscontext *ctx, jsmoduledef *m) { return js_setmoduleexportlist(ctx, m, js_test_funcs, countof(js_test_funcs)); } /* 定义初始化模块方法,由系统自动调用,且函数名称不可更改 */ jsmoduledef *js_init_module(jscontext *ctx, const char *module_name) { jsmoduledef *m; m = js_newcmodule(ctx, module_name, js_test_init); if (!m) return null; js_addmoduleexportlist(ctx, m, js_test_funcs, countof(js_test_funcs)); return m; }
将 quickjs.h
、quickjs-libc.h
、libquickjs.a
拷贝到当前工程目录下。
执行命令
gcc c_test_m.c libquickjs.a -fpic -shared -o libtest.so
生成libtest.so
文件。
1.7.3. 使用.so模块
创建js文件 c_test_m.js
import { testadd , testaddstr} from 'libtest.so' console.log('\n') console.log(`testadd: ${testadd(1, 0.5)}`) console.log('\n') console.log(`testaddstr: ${testaddstr('pi equal to about ', 3.14159)}`) console.log('\n')
qjs c_test_m.js
输出:
argc = 2
a = 1
b = 0.500000
argv[1].u.float64 = 0.500000
testadd: 1.5
js_tocstring(ctx, argv[0]) = pi equal to about
argv[1].u.float64 = 3.141590
testaddstr: pi equal to about 3.14