用途
moonbit-c-binding 是一项用于指导把任意 C 库绑定到 MoonBit 的技能,基于 MoonBit 的原生 FFI 能力。当出现以下情形时适用:为 C 库添加 extern "c" fn 声明、编写 C 存根文件(涉及 moonbit.h 与 MOONBIT_FFI_EXPORT)、为原生构建配置 moon.pkg(native-stub、link.native)、为 FFI 参数选择 #borrow 还是所有权转移、用外部对象与终结器包装 C 句柄、实现回调跳板(闭包或 FuncRef)、在 MoonBit(UTF-16)与 C(UTF-8)之间转换字符串、运行 AddressSanitizer 来发现绑定中的内存问题。
主要能力
该技能以分阶段工作流和决策表的形式给出绑定方法:
-
类型映射:在写声明之前先把 C 类型对应到 MoonBit 类型,例如 int/int32_t 对应 Int、uint32_t 对应 UInt、int64_t 对应 Int64、uint64_t 对应 UInt64、float 对应 Float、double 对应 Double、bool 在 C ABI 中按 int32_t 传递、uint8_t/char 对应 Byte、void 对应 Unit(仅返回类型)、GC 托管的 void * 用带终结器的外部对象、由 C 托管生命周期的 void * 用带 #external 注解的类型、const uint8_t */uint8_t * 对应 Bytes 或 FixedArray[Byte](C 不存储时用 #borrow)、const char * 对应 Bytes、小且无需清理的 struct * 用 struct Foo(Bytes),需要清理的 struct * 用带终结器的外部对象、enum/flags 对应 UInt/Int/常量枚举、回调函数指针对应 FuncRef 或闭包、输出参数 int * 对应 Ref[Int]。
-
项目配置(Phase 1):在 moon.mod.json 中设置 "preferred-target": "native",使 moon build、moon test、moon run 默认使用原生后端;在 moon.pkg 中通过 options 配置 "native-stub"(要编译的 C 源文件,必须与 moon.pkg 同目录)、targets(把 .mbt 文件限定到某后端,如 "ffi.mbt": ["native"])、link(native("cc-flags": ...))(编译标志 -I、-D,仅限系统库)、link(native("cc-link-flags": ...))(链接标志 -L、-l,仅限系统库)、link(native("stub-cc-flags": ...))(仅用于存根文件的编译标志)、link(native(exports: ...))(把 MoonBit 函数导出到 C 的反向方向)。注意事项:避免使用 supported-targets: ["native"],它会阻止下游包在其他目标上构建,应改用 targets 限定单个文件;设置 cc 会禁用调试构建的 TCC,带 -I/-L 的 cc-flags 会破坏 Windows 可移植性,这些仅应用于系统库。
-
FFI 层(Phase 2):extern 声明与 C 存根一起编写,extern 保持私有,安全封装留到 Phase 3。extern "c" 与 extern "C" 均有效,选定一种大小写并保持一致(例如同时面向 JS 时与 extern "js" 对齐)。外部对象模式用于需要在 GC 时清理的 C 句柄,通过 moonbit_make_external_object 创建,终结器只释放内部 C 资源,绝不 free 容器本身。#external 注解模式用于由 C 完全管理指针生命周期的场景,指针以原始 void* 传递、不进行引用计数,无需 C 存根包装或 moonbit_make_external_object。#external 是注解,需单独一行写在类型声明之前。所有权注解方面:#borrow(param) 用于 C 仅在调用期间读取、不存储引用;#owned(param) 用于所有权转移给 C、C 用完必须 moonbit_decref;每个非原始类型参数都要标注;原始类型按值传递无需注解;不确定 C 是否存储引用时不要用 #borrow;输出参数用 Ref[T] 配合 #borrow。字符串转换上,MoonBit Bytes 由运行时保证以 null 结尾,可直接传给期望 const char * 的 C 函数;反向用 moonbit_make_bytes + memcpy,MoonBit 侧用 @utf8.decode_lossy 解码(需在 moon.pkg 中 import "moonbitlang/core/encoding/utf8")。小结构体无清理需求时采用 Value-as-Bytes 模式。moonbit.h 核心 API 包括 moonbit_make_external_object、moonbit_make_bytes、moonbit_incref、moonbit_decref、Moonbit_array_length、MOONBIT_FFI_EXPORT,完整 API 可阅读 $MOON_HOME/lib/moonbit.h(默认 MOON_HOME 为 ~/.moon)。
-
MoonBit API(Phase 3):在原始 extern 之上构建安全的公开封装,包括类型声明(不透明类型由外部对象支撑、struct Settings(Bytes) 值类型等)、安全构造器与方法、错误映射。回调模式(FuncRef、闭包、跳板)另见引用文档。
-
测试(Phase 4):执行 moon test --target native -v,并用 AddressSanitizer 运行以捕获内存问题,命令为 python3 scripts/run-asan.py --repo-root <project-root> --pkg moon.pkg --pkg main/moon.pkg。
使用方式要点
技能提供决策表把常见情形映射到模式与关键动作,例如:C 仅在调用期间读取指针用 #borrow(param)(C 中不 decref);C 取得指针所有权用 #owned(param)(C 必须 moonbit_decref);C 句柄需在 GC 时清理用外部对象加终结器;C 指针由 C 管理生命周期用 #external 注解(不做 GC 跟踪,显式调用 C 的 destroy);小 C 结构体无需清理用 Value-as-Bytes;C 失败返回 null 用可空封装(检查 null,返回 Option 或抛错);带数据参数的回调用 FuncRef + Callback 技巧;不带数据参数的回调用 FuncRef;C 字符串(UTF-8)输出用 Bytes 跨 FFI;输出参数(int *result)用 Ref[T] 配合 #borrow。
若在 plan 模式下为 moonbit-c-binding 工作制定计划,技能要求在最终计划文件开头加入明确指令,让执行代理在开始实现前先阅读该技能,例如:在开始实现之前,使用 Skill 工具加载 moonbit-c-binding 技能,它提供关于 FFI 声明、所有权注解、C 存根与 AddressSanitizer 验证的全面指导。
常见陷阱
- 在 C 会存储指针时使用 #borrow:GC 可能在 C 持有陈旧引用时回收对象,只应为调用范围内的访问使用 borrow。
- 忘记对 owned 参数调用 moonbit_decref:每个非 borrow、非原始类型参数都会把所有权转移给 C,漏掉会内存泄漏。
- 对外部对象容器调用 free():容器由 GC 管理,终结器只能释放内部 C 资源。
- 对有内部指针的结构体使用 moonbit_make_bytes:Bytes 没有终结器,内部堆分配会泄漏,应改用外部对象。
- 回调调用前缺少 moonbit_incref:C 回调进入 MoonBit 时 GC 可能运行,需在调用前 incref 由 MoonBit 管理的对象,调用后 decref。
- 忘记 MOONBIT_FFI_EXPORT 宏:没有它函数对 MoonBit 链接器不可见。
适用场景与限制
适用场景包括把现有 C 库接入 MoonBit 原生目标、编写 FFI 声明与 C 存根、处理所有权与内存管理与字符串编解码、实现回调、以及用 AddressSanitizer 验证绑定内存安全。限制方面:该技能面向原生 target,配置中通过 targets 限定文件而非使用 supported-targets;部分编译/链接标志(cc、cc-flags 中的 -I/-L)会影响可移植性,仅应用于系统库;带内部指针的结构体不能使用 Bytes 模式。技能本身的具体收费、价格或平台信息在产品资料中暂未提供。
参考资料
技能引用了以下文档(原文未展开内容):@references/ownership-and-memory.md、@references/callbacks.md、@references/including-c-sources.md、@references/asan-validation.md。