引言
我们接入 QuickJS 引擎主要目的是为了给我们的应用增加支持 JS 的能力。而 JS 的能力构成则离不开引擎提供的各种 API 的支持,本文将主要介绍几种常见的 API 使用方式。
相关文章
API 浅析
QuickJS 引擎提供的 API 主要定义在 quickjs.h 中,下面我们结合源码中 example point.c 的代码进行逐个讲解。
首先解释下 example 中的 point.c 是定义了一个 module ,在这里我们通过命令编译 point.c 之后再次执行我们的 JavaScript 脚本时导入这个模块就可以用到这个模块能力。
下面将带着问题去看看我们如何使用 QuickJS 提供的 API 去解决,问题是递进提出,解决问题时需要用到的 API 也会越来越多。
一:如何在 C/C++ 代码中执行一个脚本
试想以下这么一个场景,我要用 QuickJS 引擎执行一段脚本,那么标准流程应该是怎样的呢?
首先要了解在 QuickJS 引擎的定义中,每个 JS 脚本的执行都离不开其运行时环境 JSRuntime ,因此为了执行这个脚本我们需要创建一个 JSRuntime 为后续执行脚本提供环境。
c++
JSRuntime *rt = JS_NewRuntime();
提供环境后还无法执行脚本,要想执行脚本还需要根据 JSRuntime 去构建出脚本需要执行的 JSContext ,这个才是每个脚本最终得以运行的最终环境,同时每个 JSContext 都有一个运行时环境,在这个 JSContext 中我们可以为每个脚本定制不同的扩展等。
c++
JSContext *ctx = JS_NewContext(rt);
准备好 JSContext 后就可以执行脚本了,此时不同于前一篇文章所使用的 JS_EvalFunction 在这里我们要使用 JS_Eval 方法去执行我们的脚本。这两者的区别后面在解答,继续我们的下一步。
c++
// 参数解析
// JSContext 运行脚本的上下文
// 要运行的 JS 脚本 char 数据
// JS 脚本长度
// JS 脚本文件名,若无可以传 nullptr
// 执行的 Flag(JS_EVAL_TYPE_GLOBAL/JS_EVAL_TYPE_MODULE 等)
JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,
const char *filename, int eval_flags);
经过在 C/C++ 代码中执行这个接口后我们就可以得到一个 JS 脚本的执行返回值了。
二:如何在 C/C++ 代码中增加自定义变量
经过问题一的解决,我们知道了在 C/C++ 环境下运行一个 JS 脚本必不可少的环境步骤有 JSRuntime 和 JSContext 的构建,那么关于问题二又有什么不同呢?
问题一中说到 JSContext 是我们执行脚本的真实依据,根据这个描述,我们可以知道如果要为脚本增加自定义 filed 可以从 JSContext 入手。
拓展知识,在 JS 标准中,任何 JS 引擎产生的 JSContext 都需要挂载一个 global 变量,因此为了增加自定义变量我们可以从这里入手。
c++
// 获取 global 变量
auto global = JS_GetGlobalObject(ctx);
// 构造 testValue 变量,这里为 int 值为 2333
auto testValue = JS_NewInt64(ctx, 2333);
// 为 global 变量增加 test 变量,值为 testValue
JS_SetPropertyStr(ctx, global,"test", testValue);
经过上述步骤后,我们就成功为 ctx 的 global 变量挂载了 key 为 test 的变量,后续凡是使用这个 ctx 执行的脚本获得的 test 都是 2333 。
例如:
javascript
console.log(test)
将会输出: 2333
当然,现实项目中不会有这么简单的变量需要赋值,但是更加复杂的变量都可以借鉴此方法进行新增属性,在之后的脚本中就可以根据这些属性做一些通用的操作。
三:如何导入一个自定义 module
这个可以用 example 中的 point.c 为例,这个类最主要的作用就是声明了一个 module ,使用这个类编译出来的目标产物在我们的 JS 脚本中进行 import 后就可以正常使用这个 module 提供的功能。
首先简析一下 point.c 的主要结构。
这个类包括一个结构体 JSPointData 的声明,一个 JSClassID 声明,几个静态函数,比较重要的是 module 的入口方法 js_init_module ,经过这个方法我们才能正常的加载这个 module 使用它提供的功能。
point 提供的主要功能为:
c++
static const JSCFunctionListEntry js_point_proto_funcs[] = {
JS_CGETSET_MAGIC_DEF("x", js_point_get_xy, js_point_set_xy, 0), // 获取和设置 x
JS_CGETSET_MAGIC_DEF("y", js_point_get_xy, js_point_set_xy, 1), // 获取和设置 y
JS_CFUNC_DEF("norm", 0, js_point_norm), // 得到 x 和 y 的 nrom 值
};
1, 准备环境,若为 mac 则通过 brew 安装 quickjs ,若为 ubuntu 可以使用源码 make 安装命令为:
shell
make && make install
2, 编译产物
shell
mkdir test
cp quickjs.h quickjs-libc.h libquickjs.a point.c ./test
cd test
gcc point.c libquickjs.a -fPIC -shared -o libpoint.so
至此,得到 libpoint.so
3, 编写脚本 testpoint.js 并执行验证
javascript
import { Point } from 'libpoint.so'
console.log('im test point use quickjs emebbed use.')
let p = new Point(3, 4)
let msg = 'point p x is: ' + p.x + ' y is: ' + p.y + ' norm : ' + p.norm()
console.log(msg)
执行脚本
shell
qjs testpoint.js
得到输出
shell
im test point use quickjs emebbed use.
point p x is: 3 y is: 4 norm : 5
至此,导入自定义 module 的告一段落,可以正常导入并且使用这个 moudle 的内容了。
注:这个是嵌入式的方式。。
小结
本文通过几个不同问题的解决展示了使用 QuickJS 引擎的几个方面,通过本文的阅读希望可以让大家对 QuickJS 的大致用法有一个了解,以及对各个主要的 API 用法有个初步概念。
接下来会用更加复杂的例子,去解析更多使用 QuickJS 引擎的可能性。