QuickJS原生C模块开发:从零扩展JS引擎,让JavaScript调用C函数(附完整代码)
2026/8/22 13:31:32 网站建设 项目流程

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; }

💡 三个要素的分工很清晰:

  1. 绑定函数js_fib):签名固定为(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv),负责把 JS 参数转成 C 类型、调用 C 逻辑、把结果转回 JS 值;
  2. 导出清单JS_CFUNC_DEF宏数组):第一个参数是 JS 端的函数名,第二个是期望的实参个数;
  3. 模块入口js_init_module_xxx):QuickJS 加载.so时自动查找并调用它,完成注册。

JS_CFUNC_DEFJS_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(xy属性读写);
  • 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_NewInt32JS ↔ C 数值类型转换quickjs.h
JS_GetOpaque/JS_SetOpaqueJS 对象 ↔ 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.olibquickjs.a直接链接成可执行文件,模块入口名改为js_init_module_fib即可被引擎自动发现。

八、小结:你的 QuickJS C 模块开发路线

  1. 读懂 examples/fib.c —— 掌握"函数 + 导出清单 + 模块入口"三件套;
  2. make examples/fib.so+./qjs examples/test_fib.js跑通第一个混合调用;
  3. 进阶研读 examples/point.c —— 掌握类、getter/setter 与 opaque 内存管理;
  4. 查阅 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询