# MoonBind 0.9：把 C 库接入变成可复用交付

0.9 提供有限范围的输出缓冲区契约、可恢复的资源关闭、固定来源的接入配方和可定位的升级建议。核心解析与代码生成仍由 MoonBit 实现，Python 标准库驱动器负责项目创建、调用工具链和收集报告。

## 从真实库开始

在 MoonBind 源码目录运行，需要 Python 3.10+、MoonBit 和 C11 编译器；Windows 使用 MSVC Build Tools 和 Windows SDK。

```sh
moon update
python moonbind.py init ../compression --recipe zstd
python moonbind.py validate ../compression
python moonbind.py report-check ../compression
```

`--recipe sqlite` 建立带托管句柄的 SQLite 项目，`--recipe cjson` 建立 cJSON 数组示例。配方固定 Zstandard 1.5.7、SQLite 3.45.0、cJSON 1.7.19；这些是可复现测试版本，不宣称是上游最新版本。Zstandard 的多个 C 文件分别编译，保留上游许可证；没有运行时网络下载。

`recipes/*.json` 记录来源版本、许可证、每个源文件的 SHA-256、目标平台、链接选项与契约。生成项目中的 `moonbind.recipe.lock.json` 固定其 vendor 文件。修改或缺少源文件会使生成和验证失败。升级第三方源码时，必须审查代码和契约并有意识地更新锁文件。摘要是完整性检查，不是发布者签名；修改契约后仍须执行 validate。

生成的 `moonbind-config.h` 保存配置中的宏；新模板的每个库编译单元都会包含它，更新配置后重新生成即可同步。已有手写 C 编译单元需要主动包含此文件或自行同步编译宏。受管状态兼容旧版五文件、六文件格式。仓库按 LF 换行保存配方源文件，锁文件摘要以仓库中的确切字节为准。

## 输出缓冲区

```json
{
  "output_buffers": {
    "ZSTD_compress": "dst:dstCapacity:src:srcSize:ZSTD_isError:67108864",
    "ZSTD_decompress": "dst:dstCapacity:src:compressedSize:ZSTD_isError:67108864"
  }
}
```

格式为 `目标指针:目标容量:输入指针:输入长度:错误判断函数:最大容量`。参数可用头文件名称或 `#N`（从 0 开始）。六项都是显式契约。长度、容量和返回值必须是同宽的无符号 32/64 位整数，错误判断函数接受该返回类型、返回 int/unsigned int，非零表示失败。

生成 `*_bytes(input : Bytes, capacity : Int, 其余参数...) -> Result[Bytes, MoonBindBufferError]`。配方重命名 Zstandard API，因此压缩调用为 `zstd_compress_bytes(input, capacity, level)`。

- 分配前拒绝负容量和超过契约上限的容量。
- 从实际 Bytes 得到输入长度，不让调用方单独填错长度。
- 先运行上游错误判断函数，再检查返回长度不超过容量，最后才转为 Int 并复制有效字节。
- 返回 `InvalidCapacity`、`BufferCallFailed` 或 `InvalidWrittenLength`，保留完整 64 位错误值。
- 空输入和零容量合法与否由 C 库决定；Zstandard 空数据压缩仍需容纳帧头。

此接口用于同步、不保留指针的 C 函数。C 必须遵守容量约定；返回后检查不能修复 C 已经越界写入的内存。返回值通过复制裁剪，输出内存峰值可接近两倍容量；分配失败不是可恢复 Result。原始 FFI 保留，不能把包装器描述为完整内存安全证明。

### 实际文件

```sh
cd ../compression
moon update
moon run file --target native -- compress input.bin output.zst
moon run file --target native -- decompress output.zst restored.bin
```

输出路径必须尚不存在。文件示例将输入完整读入内存后检查大小，输入和输出策略上限均为 64 MiB，压缩的最坏情况上界也须落在限额内；解压输出预分配 64 MiB。大文件、未知大小的持续数据流应使用流式接口，本版本尚未提供。文件示例的存在性检查用于避免日常误覆盖，不是并发文件写入锁。

## 输出句柄与关闭失败

```json
{
  "status_codes": { "sqlite3_open": "0", "sqlite3_prepare_v2": "0" },
  "out_params": { "sqlite3_open": "ppDb", "sqlite3_prepare_v2": "ppStmt" },
  "null_params": { "sqlite3_prepare_v2": "pzTail" },
  "managed_outputs": {
    "sqlite3_open": "sqlite3_close:0:retained",
    "sqlite3_prepare_v2": "sqlite3_finalize:0:released"
  }
}
```

格式为 `释放函数:成功码:失败时所有权策略`。释放函数必须是匹配的 `int(T*)`。

- `retained`：关闭报错后句柄仍有效，可以排除原因后重试。SQLite 尚有未释放语句时 sqlite3_close 返回 BUSY，数据库仍由调用方持有。
- `released`：即使报告错误，资源也已释放，包装器必须进入关闭状态。例如 sqlite3_finalize 可能返回前一次执行错误，但语句已被销毁。

生成 `*_managed` 工厂与私有状态包装器，提供 `with_handle`、`close`、`is_closed`。错误工厂结果仍保留非空资源，供调用者清理。重复关闭、关闭后的使用、借用期间的关闭会在再次进入 C 前被拒绝；回调抛错会恢复借用计数。

必须显式 close，无 GC 自动释放。`with_handle` 中不能私自释放或让原始指针逃逸；包装器不会追踪所有父子资源，也不是跨线程同步机制。旧 `with_*` scoped ownership API 保持原语义，不会自动获得可恢复句柄能力；需要重试清理的 T** 接口使用新包装器。

## 升级报告

```sh
python moonbind.py upgrade ../project --against /path/to/old/api.h
```

报告分为 `must_change`（契约无法生成、选中函数被移除）、`human_review`（结构或依赖契约发生变化）、`no_detected_impact`。报告提供配置 JSON Pointer、已有绑定声明行号、相关运行检查。形参同位置、同类型改名时，给出带旧值前置条件的配置替换建议；所有建议 `applied: false`，不改写绑定或配置。

纯参数改名独立记录，不算 ABI 变化，但仍可能使名称契约失效。未发现结构变化不意味着语义、生命周期或二进制兼容已经被证明。旧头文件的传递包含文件仍须可访问。错误或待审报告不能代替修改后的重新生成、编译和运行。

## 验证与边界

`python scripts/verify-v09.py` 验证三种真实库、真实文件往返、32/64 位长度检查、超限分配拒绝、错误输出句柄、两种关闭失败策略、源码校验和升级建议不覆盖已有文件。旧版回归继续保留。CI 配置覆盖 Linux x64、macOS arm64、Windows x64；发布以对应提交实际 CI 结果为准。

尚未支持异步/跨线程回调、完整 C++、通用流式缓冲区协议、自动推断所有权。两位独立开发者的真实试用任务见 `user-trial-v09.md`；目前没有用自动化测试冒充用户反馈。
