欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

QuickJS 快速入门 (QuickJS QuickStart)

程序员文章站 2022-12-21 21:54:47
1. QuickJS 快速入门 (QuickJS QuickStart) "1. QuickJS 快速入门 (QuickJS QuickStart)" "1.1. 简介" "1.2. 安装" "1.3. 简单使用" "1.3.1. 控制台执行" "1.3.2. js脚本执行" "1.3.3. 编译二 ......

1. quickjs 快速入门 (quickjs quickstart)

1.1. 简介

quickjs是一个小型的可嵌入javascript引擎。它支持es2020规范,包括模块、异步生成器和代理。它还支持数学扩展,比如大整数(bigint)、大浮点数(bigfloat)和操作符重载。

1.2. 安装

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.hquickjs-libc.hlibquickjs.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.hstdio.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模块编写

流程如下:

  1. 自定义原生c函数
  2. 定义 quickjs c 函数
  3. 定义api的函数入口名称及列表
  4. 定义初始化回调方法,将函数入口列表在模块中暴露
  5. 定义初始化模块方法,由系统自动调用,且函数名称不可更改

创建编写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.hquickjs-libc.hlibquickjs.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