程序员的知识教程库

网站首页 > 教程分享 正文

「实操演示」比肩V8的QuickJS入门指南

henian88 2024-08-12 19:46:48 教程分享 79 ℃ 0 评论


2019年末的某天,我在某个科技网站的报道中似乎听到了:神级程序员Fabrice Bellard手撸了一个类V8引擎,编译后不到1M,精简后可低至500k。当时马上到搜索引擎中寻找,找到了QuickJS的主页链接 bellard.org/quickjs 。抽空快速看了一下文档,立刻惊为天人,这不就是一个微型NodeJs核心吗?当时就打算入手试试,但手上没时间,就一直拖到了今天。
这2天刚好休息,利用空闲时间动手了解了一下,以下做一个简单的实操教程,稍后有空将会制作成视频教程。

注:以下为萧鸣本人全程实操,所有程序调用及编译都正常通过,并且得到最后结果,确保不浪费大家宝贵的时间。


QuickJS是什么?

QuickJS是一个小型且可嵌入的Javascript引擎。它支持 ES2020规范,包括模块,异步生成器,代理和BigInt。它可选地支持数学扩展,例如大十进制浮点数(BigDecimal),大二进制浮点数(BigFloat)和运算符重载。

内置标准std的实现、os模块文件与系统操作模块的实现、多线程Worker、正则表达式、完整的模块引入机制,以及自定义动态链接库的引入。

它的qjsc编译器,可以将你的js文件编译成单个可执行文件,通过优化后,体积可以缩小至500k左右,所以它非常适合使用在嵌入式设备,或者嵌入到其他项目中去。

QuickJS是基于MIT协议发布的,大家可以访问它的官网并下载源码进行研究学习

神级作者

1972年生的FabriceBellard,神级程序员之一。



说说他的作品你就明白为什么称为神级了:

FFmpeg(流媒体和视频制作哪个不是用它做底层,20年前年幼的我看到各种解霸,感觉人家做媒体解码软件的太牛了,结果现在一看,bin文件夹下赫然躺着FFmpeg)

QEMU 最牛的模拟器谁用谁知道

他也是最快圆周率算法贝拉公式、TCCBOOT和TCC(微型C编译器)

好像还抽空搞了一个 TinyOpenGL

还用js写了一个可以运行在浏览器上的jsLinux(请自行查阅)

安装方式:源码安装(Linux与MacOS)

使用make命令来安装.可以不使用make install,直接在当前文件夹下可以找到编译后的文件

make && make install  

mac下可以直接用homebrew安装

brew install quickjs

安装成功之后,我们可以直接执行qjs命令,我们可以看到我们进入了一个交互式编程界面,熟悉Chorme控制台或者Python编程的同学,应该理解在里面进行编码实验。


交互式编程界面环境中,默认导入了std和os模块,可以直接使用,在js文件中,则需要导入模块后使用。

使用qjs来执行一个js文件

编辑一个js文件,保存为test.js

/* 引入os模块 */
import * as os from 'os'; 

/* setTimeout方法在os模块中*/
os.setTimeout(() => {  
    console.log("hello 萧鸣!")
}, (500)); 

保存文件,我们用qjs来解释执行它

> qjs test.js

得到如下执行结果


用qjsc将test.js 编译成单个可执行文件

必须将源码中的quickjs.h、quickjs-libc.h、libquickjs.a 三个文件拷贝到test.js同目录下

如果怕麻烦,可以把你的文件拷贝到源码中进行编译

> qjsc test.js -o test 
> ls
> ./test

这种编译成单个二进制文件的形式,不依赖任何外部环境与程序,去除一些当前项目不需要的库(比如正则库等)可以极大减小二级制文件的体积,可以精简至500k左右。

而且因为QuickJS里内置了Worker,以及可以通过exec来调用外部程序,我们可以用它来做很多事情,比如文件监听、进程守护等。

内置模块快速预览(具体请查询官网)

std模块:

包含了常用的evalScript、loadScript、loadFile、open、popen、fdopen、tmpfile、puts、printf、sprintf、strerror、gc、getenv、urlGet、parseExtJSON

包装了libc的stdin、stdout、stderr。

Error,包含了常见的枚举对象,并可以自定义其他错误代码:EINVAL、EIO、EACCES、EEXIST、ENOSPC、ENOSYS、EBUSY、ENOENT、EPERM、EPIPE

FILE原型:

close、puts、printf、flush、seek、tell、tello、eof、error、clearerr、read、write、getline、readAsString、getByte、putByte

os模块:提供了操作系统特定的 低级文件访问、信号、计时器、异步IO、线程 方法

open、close、seek、read、write、isatty、ttyGetWinSize、ttySetRaw、remove、rename、realpath、getcwd、chdir、mkdir、stat、lstat、utimes、symlink、readlink、readdir、setReadHandler、setWriteHandler、signal、kill、exec、waitpid、WNOHANG、dup、dup2、pipe、sleep、setTimeout、clearTimeout、platform、Worker

Unicode

QuickJS实现了特定的Unicode库,不用依赖外部的大型Unicode库(比如ICU)。所有的Unicode表都经过了压缩来提高访问速度(果然神级程序员)。

如何自己扩展 QuickJS C API

C API的设计简单有效,C API在 quickjs.h中进行定义

JS数据类型的C定义

以下结构定义在quickjs.h文件中

typedef struct JSValue {
    /*用来存放指针或数组*/
    JSValueUnion u;
    /*标记 undefined 等*/
    int64_t tag;           
} JSValue;


typedef union JSValueUnion {
    /*指针*/
    void *ptr; 
    /*整型*/
    int32_t int32;
    /*浮点*/
    double float64;
} JSValueUnion;

编写一个C模块

关键点在于定义API函数的入口、定义初始化的回调函数并暴露处理、定义初始化模块方法(系统自动调用,这个函数名称只能遵循qjs的规则)

/* 创建文件 test.c */

#include "quickjs.h"
#include "stdio.h"
#include "stdlib.h"

#define len(x) (sizeof(x) / sizeof((x)[0]))

/* 功能函数,实现你的具体功能的C函数 */
static double test_add(int a, double b)
{
    return a + b;
}


/*  *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; }
        
    return JS_NewFloat64(ctx, test_add(a, b)); 
}


/*  js函数入口名称及列表*/

static const JSCFunctionListEntry js_test_funcs[] = {
    
    /* 表示入口名称是testAdd,2个参数,调用内部哪个函数*/
    JS_CFUNC_DEF("testAdd", 2, js_test_add), 
};


/* 初始化的回调方法(由系统调用),将函数入口列表暴露出来*/

static int js_test_init(JSContext *ctx, JSModuleDef *m)
{
    return JS_SetModuleExportList(ctx, m, js_test_funcs, len(js_test_funcs));
}


/* 模块初始化,由系统调用,名称和参数不可修改,遵循qjs的规则*/

JSModuleDef *js_init_module(JSContext *ctx, const char *module_name)
{
    JSModuleDef *module;
    module = JS_NewCModule(ctx, module_name, js_test_init);
    if (!module){ return NULL; }
    JS_AddModuleExportList(ctx, module, js_test_funcs, len(js_test_funcs));
    return module;
}
gcc test.c libquickjs.a  -fPIC -shared -o test.so

萧鸣注: 这里会碰到一个坑,可能在编译期间会提示你:

libquickjs.a无法创建动态链接库,要使用 -fPIC进行编译

what?我不是用了 -fPIC参数了吗?实际上这里说的是整个qjs源码make时要加入-fPIC参数

我们找到源码里的makefile,打开找到以下内容

HOST_CC=gcc
CC=$(CROSS_PREFIX)gcc
CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d

然后在 CFLAGS 这行增加参数 -fPIC, 然后重新make即可

CFLAGS=-g -fPIC -Wall -MMD -MF $(OBJDIR)/$(@F).d


在js脚本中调用这个动态链接库

import { testAdd} from 'test.so'
console.log(`萧鸣测试 testAdd(200,10.5)`)
console.log(`结果等于: ${testAdd(200, 10.5)}`)

执行后得到以下结果



总结

经过本人亲手试验, QuickJS可以灵活地使用在一些嵌入式程序之中,因为体积小可以嵌入到单片机中使用,通过自己扩展c方法,提供js对单片机的控制。

同时也可以参考cocos2d-x之类游戏引擎,利用嵌入的Quickjs来操纵底层的图形图像接口,来做一个自己的游戏引擎。

也可以嵌入到一些软件中去,用js来调用软件内部的功能。

也可以嵌入到安卓和苹果app中去,形成自己动态的脚本配置。

总之只有想不到没有做不到。

其他:

后续更多对于QuickJS的尝试,比如将QuickJS嵌入到nginx中去,直接在nginx中提供js脚本提供web服务等,将在后续的视频教程中奉献给大家。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表