QuickJS 快速入门 (QuickJS QuickStart)
1. QuickJS 快速入门 (QuickJS QuickStart)1.1. 简介QuickJS是一个小型的可嵌入Javascript引擎。它支持ES2020规范,包括模块、异步生成器和代理。它还支持数学扩展,比如大整数(BigInt)、大浮点数(BigFloat)和操作符重载。1.2. 安装Linux 直接下载 源码make && make install...
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
和其他一些实用程序。
1.5.1. 常见的导出函数
exit(n)
退出进程。evalScript(str)
将字符串str
以脚本方式运行(全局eval)。loadScript(filename)
将文件filename
以脚本方式运行(全局eval)。Error(errno)
是 std.Error构造函数。错误实例包含字段errno
(错误代码)和message
(std.Error.strerror(errno)
的结果)。
构造函数包含以下字段:EINVAL
、EIO
、EACCES
、EEXIST
、ENOSPC
、ENOSYS
、EBUSY
、ENOENT
、EPERM
、EPIPE
常见错误的整数值 (可以定义附加错误代码)。strerror(errno)
返回描述错误的字符串errno
。open(filename, flags)
打开一个文件(libc的包装器fopen()
)。在I/O错误时抛出std.Error
。tmpfile()
打开一个临时文件。在I/O错误时抛出std.Error
。puts(str)
相当于std.out.puts(str)
。printf(fmt, ...args)
相当于std.out.printf(fmt, ...args)
、sprintf(fmt, ...args)
相当于libc的sprintf()。in
、out
、err
包装libc文件的stdin
、stdout
、stderr
。SEEK_SET
、SEEK_CUR
、SEEK_END
是 seek() 的常量。global
引用全局对象。gc()
手动调用循环删除算法。循环移除算法在需要时自动启动,因此该功能在特定内存限制或测试时非常有用。getenv(name)
返回环境变量的值name
,或未定义时返回undefined
.
1.5.2. FILE 原型
close()
关闭文件。puts(str)
使用UTF-8编码输出字符串。printf(fmt, ...args)
格式化printf,与libc printf格式相同。flush()
刷新缓冲的文件。seek(offset, whence)
寻找特定文件位置 (从哪里std.SEEK_*
)。在I/O错误时抛出std.Error
。tell()
返回当前文件位置。eof()
如果文件结束,则返回true。fileno()
返回关联的OS句柄。read(buffer, position, length)
(封装 libcfread
)。write(buffer, position, length)
(封装 libcfread
)。getline()
返回文件中的下一行,假设为UTF-8编码,不包括尾随换行符。getByte()
返回文件中的下一个字节。putByte(c)
将一个字节写入文件。
1.5.3. 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。
如果是OK,OS函数通常返回0,或者OS返回特定的错误代码。
可用导出函数:
open(filename, flags, mode = 0o666)
打开一个文件。如果错误,返回句柄或<0。O_RDONLY
、O_WRONLY
、O_RDWR
、O_APPEND
、O_CREAT
、O_EXCL
、O_TRUNC
POSIX打开标志。O_TEXT
(Windows特定)。以文本模式打开文件。默认为二进制模式。close(fd)
关闭文件句柄fd
。seek(fd, offset, whence)
寻找文件。使用std.SEEK_*
或whence
。read(fd, buffer, offset, length)
读取指定长度的字节码。返回读取成功的字节码长度,失败返回 <0;write(fd, buffer, offset, length)
写入字节码,失败返回<0。isatty(fd)
fd
是一个TTY (终端)句柄返true
。ttyGetWinSize(fd)
返回TTY大小[width, height]
或者如果不可用返回null
。ttySetRaw(fd)
在原始模式下设置TTY。remove(filename)
删除文件。如果正常则返回0,如果错误则返回<0。rename(oldname, newname)
重命名文件。如果正常则返回0,如果错误则返回<0。setReadHandler(fd, func)
将读处理程序添加到文件句柄fd
。fd
每次有数据待增加处理时调用func
。支持每个文件句柄的单个读处理程序。使用func = null
来删除句柄。setWriteHandler(fd, func)
将写处理程序添加到文件句柄fd
。fd
每次有数据待写入处理时调用func
. 支持每个文件句柄一个写处理程序。使用 `func = null来删除句柄。signal(signal, func)
当信号signal
发生时调用func
。 每个信号编号只支持一个处理程序。使用null
设定的默认处理或undefined
忽略的信号。
SIGINT
SIGABRT
SIGFPE
SIGILL
SIGSEGV
SIGTERM
POSIX 信号编号。setTimeout(func, delay)
在delay
毫秒之后调用函数func
。返回计时器的句柄。clearTimer(handle)
取消计时器。platform
返回表示该平台的字符串:"linux"
,"darwin"
,"win32"
or"js"
。
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
更多推荐
所有评论(0)