引言
我们接入 QuickJS 引擎的目的是为了使用引擎能力,那么应该从哪里获取 QuickJS 支持那些能力呢,比较好的一个来源就是 QuickJS 源码。
相关文章
源码阅读
为了对 QuickJS 引擎有稍微深入的了解,我们需要阅读源码以加深对源码的理解,并且通过源码的阅读找寻当下我们使用引擎可能会用到的 API。
目前我个人是通过几个维度来阅读和分析源码,它们分别是头文件阅读与分析 / 源码 Test Code 阅读 / 引擎核心实现源码阅读。经过这三种维度的源码阅读后会对整体的引擎代码有一个初步了解。有了这些基础后就可以粗略选择出来可能要使用的 API 接口。
头文件
由于 QuickJS 是用 C 语言写的,我们在使用的时候可以优先查看代码通过头文件暴露了那些方法。
这里我以比较重要的 quickjs.h 为例,分析 QuickJS 引擎都提供了那些能力。
从上至下阅读,首先可以看到 QuickJS 对各种 reference 的 tag 定义,如 JS_TAG_INT = 0 / JS_TAG_BOOL = 1 等。
继续往下可以看到 QuickJS 对核心结构体的定义,如 JSValue / JSValueUnion 等。
然后在这个头文件里声明了一些常用的方法,如 #define JS_VALUE_GET_TAG(v) 获取 JSValue 的 tag 等。
最后是一些方法的定义,如 JSRuntime *JS_NewRuntime(void); 产生 JSRuntime 的结构等。
有了这个整体头文件的分析,可以大致对 QuickJS 引擎有一个初步的了解。经过这一宏观的了解后就可以根据 QuickJS 源码中提供的测试代码进行阅读来进一步的熟悉 QuickJS 的架构和基本用法。
Example Code
hello.c
通过编译 QuickJS 的源码会生成一个 test 文件 hello.c 通过对这个文件的阅读,我们可以了解最典型的 QuickJS 引擎用法。
c
int main(int argc, char **argv)
{
JSRuntime *rt;
JSContext *ctx;
rt = JS_NewRuntime();
js_std_set_worker_new_context_func(JS_NewCustomContext);
js_std_init_handlers(rt);
ctx = JS_NewCustomContext(rt);
js_std_add_helpers(ctx, argc, argv);
js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0);
js_std_loop(ctx);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
通过上述代码可以分析出,在使用 QuickJS 引擎时,首先要创建一个 JSRuntime 环境,然后通过这个 Runtime 去生成对应的 JSContext 后续的执行则依赖这个 JSContext 去使用。
由于 QuickJS 引擎初衷有一部分是为嵌入式设备使用,因此从 js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0); 这个方法里也可以看出 QuickJS 是支持直接通过二进制字节码运行脚本代码的。
这个 hello.c 文件驱动的方法都是通过调用 quickjs-libc.h 实现的。通过研究这个文件的实现类 quickjs-libc.c 可以看到大部分的典型用法。
如 js_std_eval_binary 方法中最终是调用 JS_EvalFunction 执行输入的字节码二进制文件,在 js_std_add_helpers 方法中完成对应方法的注入等。
point.c
这个 example 主要是讲解如何自定义自己的 JS Class 以及在 JS 脚本中使用,通过自定义实现,可以在 JS 脚本中使用我们使用 C 语言定义的结构体等。
关于本 example 的使用会在接下来的文章(API 详解)中详细解析,同时也会在该文中解析各个 API 的使用场景和注意事项。
Source Code
经过上述两种方式的阅读后,可以再通过阅读真实的 QuickJS 源码加深对引擎的理解。
阅读源码时,有时候会因为源码的体积或者架构比较复杂感到无从下手,根据我个人的经验,最好的阅读方式是带着问题去阅读和学习更加快速。
现在就以 JS_EvalFunction 为例,看下 QuickJS 引擎是如何执行 Fucntion 的实例的。
阅读代码可以发现这个方法内部调用了 JS_EvalFunctionInternal 进行下一步的执行,其中多添加了一个参数是 ctx->global_obj ,在这个方法的内部首先判断这个输入的 fun_obj 是 JS_TAG_FUNCTION_BYTECODE 还是 JS_TAG_MODULE 类型,如果是 JS_TAG_FUNCTION_BYTECODE 则可以正常执行 JS_CallFree 并且拿到结果。如果是 JS_TAG_MODULE 则会首先拿到对应的 PTR 然后对这个 PTR 进行一系列的操作,最终完成这个 module 的加载。
小结
本篇文章主要从三个维度介绍了如何阅读 QuickJS 的源码,希望通过这些方式可以加深大家对 QuickJS 的了解。
接下来将根据实际代码分析如何使用 QuickJS 引擎在自己的工程中实战。