QuickJS原生C模块开发:从零扩展JS引擎,让JavaScript调用C函数(附完整代码)
【免费下载链接】QuickJSQuickJS is a small and embeddable Javascript engine. QuickJS sources are copyright Fabrice Bellard and Charlie Gordon.项目地址: https://gitcode.com/gh_mirrors/quick/QuickJS
QuickJS 是一个小巧、可嵌入的 JavaScript 引擎。本文面向新手,带你从零完成 QuickJS 原生 C 模块开发:编写 C 函数、绑定导出、编译动态库,最终让 JavaScript 像调用普通 JS 函数一样直接调用 C 函数。全程只用一个"求斐波那契数"的例子,附完整代码,照着做即可跑通 ⚡
一、为什么给 QuickJS 扩展 C 模块?
QuickJS 的核心卖点是体积小、可嵌入:它可以作为一个库被链接进你的 C/C++ 程序中,为应用提供脚本能力。而 C 模块(C Module)机制让这种"混合编程"变得非常自然:
- 🚀性能:计算密集的逻辑用 C 实现,速度远快于纯 JS 解释执行;
- 🔌能力:JS 代码可以直接访问 C 标准库、系统 API 或你已有的 C 代码库;
- 📦标准化:C 模块使用 ES Module 的
import语法引入,与 JS 模块写法一致,学习成本极低。
QuickJS 官方仓库的 examples/ 目录内置了两个经典示例,是学习 C 模块开发的最佳起点:
| 示例 | 文件 | 说明 |
|---|---|---|
| 函数导出 | examples/fib.c | 导出一个fib函数,最简结构 |
| 类导出 | examples/point.c | 导出可继承的Point类,含 getter/setter |
二、准备工作:克隆仓库并编译 QuickJS(一键安装步骤)
只需三步就能拥有一个可用的 QuickJS 环境:
# 1. 克隆源码 git clone https://gitcode.com/gh_mirrors/quick/QuickJS cd QuickJS # 2. 编译(需要 gcc 或 clang) make # 3. 验证:运行内置 C 模块示例 ./qjs examples/test_fib.js编译完成后会生成两个重要可执行文件:
qjs:JS 解释器,运行.js脚本(本文主要用它);qjsc:JS 编译器,把 JS 静态编译成独立可执行文件(见 README.md 的 Getting Started 章节)。
如果示例输出fib(10)= 55,说明你的环境已经完全就绪 ✅
三、5 分钟上手:编写第一个 QuickJS C 模块(fib 完整代码)
一个 QuickJS C 模块只需要理解3 个要素,这也是 examples/fib.c 的完整结构(省略版权头):
#include "quickjs.h" #define countof(x) (sizeof(x) / sizeof((x)[0])) /* ① 你要暴露给 JS 的 C 函数 */ static JSValue js_fib(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int n, res; if (JS_ToInt32(ctx, &n, argv[0])) /* 参数转换失败则抛异常 */ return JS_EXCEPTION; res = fib(n); /* 调用你的 C 逻辑 */ return JS_NewInt32(ctx, res); /* 返回结果给 JS */ } /* ② 导出清单:JS 端 import 时能看到的名字 */ static const JSCFunctionListEntry js_fib_funcs[] = { JS_CFUNC_DEF("fib", 1, js_fib), }; /* ③ 模块初始化:把导出清单挂到模块上 */ static int js_fib_init(JSContext *ctx, JSModuleDef *m) { return JS_SetModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs)); } JSModuleDef *js_init_module_fib(JSContext *ctx, const char *module_name) { JSModuleDef *m = JS_NewCModule(ctx, module_name, js_fib_init); if (!m) return NULL; JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs)); return m; }💡 三个要素的分工很清晰:
- 绑定函数(
js_fib):签名固定为(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv),负责把 JS 参数转成 C 类型、调用 C 逻辑、把结果转回 JS 值; - 导出清单(
JS_CFUNC_DEF宏数组):第一个参数是 JS 端的函数名,第二个是期望的实参个数; - 模块入口(
js_init_module_xxx):QuickJS 加载.so时自动查找并调用它,完成注册。
宏JS_CFUNC_DEF、JS_SetModuleExportList等 API 的完整定义见 quickjs.h。
四、编译 fib.so 动态库并运行(最快配置方法)
编译 C 模块只需一条命令。仓库的 Makefile 已内置examples/fib.so目标,直接执行:
make examples/fib.so它会以-shared方式把fib.c编译成动态库examples/fib.so(Windows 下对应.dll,macOS 下对应.dylib)。
然后在 JS 端用标准 ES Module 语法导入即可,参考 examples/test_fib.js:
import { fib } from "./fib.so"; console.log("Hello World"); console.log("fib(10)=", fib(10));运行:
./qjs examples/test_fib.js # Hello World # fib(10)= 55就这么简单——JavaScript 已经成功调用 C 函数了 🎉 此外,Makefile 还演示了用qjsc -M examples/fib.so,fib -m把 JS 脚本连同 C 模块一起静态编译成独立可执行文件examples/test_fib。
五、进阶技巧:在 C 模块中定义可继承的 Point 类
除了导出函数,C 模块还可以导出原生 class。examples/point.c 展示了完整玩法:
JS_NewClassID+JS_NewClass:注册一个 C 类,并用JS_SetOpaque把 C 结构体(JSPointData)挂到 JS 对象上;JS_NewObjectProtoClass:构造Point实例;JS_CGETSET_MAGIC_DEF:实现 C 侧的 getter/setter(x、y属性读写);JS_CFUNC_DEF:实现实例方法(norm()求模长)。
JS 端不仅能直接new,还能继承这个 C 类(见 examples/test_point.js):
import { Point } from "./point.so"; class ColorPoint extends Point { constructor(x, y, color) { super(x, y); this.color = color; } } const pt = new Point(2, 3); console.log(pt.norm()); // 3.605551275463989 const cp = new ColorPoint(2, 3, 0xffffff); console.log(cp.x, cp.get_color?.() ?? cp.color);运行方式与 fib 相同:
make examples/point.so ./qjs examples/test_point.js这个示例覆盖的 API(构造器、原型、getter/setter、opaque 指针)正是 90% C 模块开发的用武之地,强烈建议通读一遍 examples/point.c。
六、QuickJS C 模块常用 API 速查表(新手收藏版)
| API | 作用 | 出处 |
|---|---|---|
JS_NewCModule | 创建 C 模块并指定初始化函数 | quickjs.h |
JS_AddModuleExportList | 模块入口处声明导出清单 | quickjs.h |
JS_SetModuleExportList | 初始化时批量挂载导出 | quickjs.h |
JS_CFUNC_DEF | 定义普通 C 函数导出 | quickjs.h |
JS_CGETSET_DEF | 定义 getter/setter 属性 | quickjs.h |
JS_SetPropertyFunctionList | 把函数清单挂到对象/原型上 | quickjs.h |
JS_ToInt32/JS_NewInt32 | JS ↔ C 数值类型转换 | quickjs.h |
JS_GetOpaque/JS_SetOpaque | JS 对象 ↔ C 结构体互转 | quickjs.h |
💡 记忆口诀:参数进来用JS_To*,结果出去用JS_New*,出错就return JS_EXCEPTION。
七、QuickJS C 模块开发常见问题与排查(FAQ)
Q1:运行时报 "Cannot find module './xxx.so'"?确认.so已编译且路径与import中写的相对路径一致;动态库文件必须与 JS 脚本同目录(或位于搜索路径中)。
Q2:为什么函数内部总要先JS_ToInt32这类转换?JS 是动态类型,任何值都可能传进来。转换失败时JS_To*返回非 0,此时应return JS_EXCEPTION,QuickJS 会自动把异常抛给 JS 调用方——这是最安全的错误处理方式。
Q3:Windows / macOS 下能编译.so吗?能,只是扩展名不同:Linux 为.so、macOS 为.dylib、Windows 为.dll。当前 Makefile 在 Darwin 上默认跳过共享库目标,手动加-shared(clang 下为-dynamiclib)即可。
Q4:C 模块可以不用动态库直接编译进程序吗?可以。看 Makefile 中examples/test_fib的规则:把fib.o和libquickjs.a直接链接成可执行文件,模块入口名改为js_init_module_fib即可被引擎自动发现。
八、小结:你的 QuickJS C 模块开发路线
- 读懂 examples/fib.c —— 掌握"函数 + 导出清单 + 模块入口"三件套;
- 用
make examples/fib.so+./qjs examples/test_fib.js跑通第一个混合调用; - 进阶研读 examples/point.c —— 掌握类、getter/setter 与 opaque 内存管理;
- 查阅 quickjs.h 头文件与 doc/quickjs.texi 官方文档,按需使用更多 API。
掌握这套流程后,你就可以在 QuickJS 中接入任意 C 能力——从数学库、网络通信到硬件驱动,JavaScript 的脚本灵活性加上 C 的性能与系统级能力,正是"可嵌入 JS 引擎"的最大价值所在 💪
【免费下载链接】QuickJSQuickJS is a small and embeddable Javascript engine. QuickJS sources are copyright Fabrice Bellard and Charlie Gordon.项目地址: https://gitcode.com/gh_mirrors/quick/QuickJS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考