From 7df7fe9b716ba6863269161378e330e3086be06f Mon Sep 17 00:00:00 2001 From: wangdl Date: Thu, 18 Jun 2026 14:43:53 +0800 Subject: [PATCH] docs: add C-ABI out-pointer pattern doc (DOC-FULL-031) - ARM64 iOS ABI limitation: struct pass-by-value unstable - Pattern: scalar-only params, paired out-pointers, JSON serialization - Memory management: ownership transfer, rustbuffer_free_separate - Template: write_result_to_out_ptrs + full FFI function - Current 10 functions using out-pointer pattern Co-Authored-By: Claude Opus 4.7 --- docs/c-abi-out-pointer-pattern.md | 239 ++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 docs/c-abi-out-pointer-pattern.md diff --git a/docs/c-abi-out-pointer-pattern.md b/docs/c-abi-out-pointer-pattern.md new file mode 100644 index 0000000..1b56625 --- /dev/null +++ b/docs/c-abi-out-pointer-pattern.md @@ -0,0 +1,239 @@ +# C-ABI Out-Pointer 模式文档(iOS 兼容性) + +## 1. 背景 + +ARM64 iOS ABI 对跨 FFI 边界的 struct 传参有严格限制。UniFFI 默认的 `RustBuffer` 结构体(包含 `{ capacity: i32, len: i32, data: *mut u8 }` 三个字段)在 ARM64 上不保证稳定传递。 + +**问题表现**:函数返回的 `RustBuffer` 在 iOS 真机上可能得到损坏的 capacity/len 值,导致数据丢失或 crash。 + +**解决方案**:C-ABI out-pointer 模式 — 将 struct 成员拆分为独立的标量参数/返回。 + +--- + +## 2. 模式对比 + +### 2.1 Standard UniFFI(有问题) + +```rust +// ❌ ARM64 不稳定:RustBuffer struct 跨 FFI 传参 +#[uniffi::export] +fn parse_markdown(content: String) -> Result, DocumentError> { + // UniFFI 自动生成 RustBuffer 序列化 +} +``` + +生成的 C-ABI: +```c +// ❌ 返回 struct — ARM64 可能损坏 +RustBuffer ffi_parse_markdown(RustBuffer content, RustCallStatus* status); +``` + +### 2.2 Out-Pointer 模式(安全) + +```rust +// ✅ 所有参数/返回值均为标量(int/pointer) +#[no_mangle] +pub extern "C" fn ffi_parse_markdown_separate( + content_len: i32, + content_data: *const u8, + out_result_capacity: *mut u64, + out_result_len: *mut u64, + out_result_data: *mut *mut u8, + out_error_capacity: *mut u64, + out_error_len: *mut u64, + out_error_data: *mut *mut u8, +) +``` + +生成的 C-ABI — 所有参数为标量,兼容 ARM64。 + +--- + +## 3. 通用 Out-Pointer 模板 + +### 3.1 Result 序列化 + +```rust +/// Write Result into out-pointers. +fn write_result_to_out_ptrs( + result: Result, DocumentError>, + out_result_cap: *mut u64, out_result_len: *mut u64, out_result_data: *mut *mut u8, + out_error_cap: *mut u64, out_error_len: *mut u64, out_error_data: *mut *mut u8, +) { + match result { + Ok(value) => { + let buf = uniffi::RustBuffer::from_vec(value); + unsafe { + *out_result_capacity = buf.capacity() as u64; + *out_result_len = buf.len() as u64; + *out_result_data = buf.data_pointer() as *mut u8; + *out_error_capacity = 0; + *out_error_len = 0; + *out_error_data = std::ptr::null_mut(); + } + std::mem::forget(buf); // caller now owns the buffer + } + Err(e) => { + let err_json = serde_json::to_string(&e).unwrap_or_default().into_bytes(); + let buf = uniffi::RustBuffer::from_vec(err_json); + unsafe { + *out_result_capacity = 0; + *out_result_len = 0; + *out_result_data = std::ptr::null_mut(); + *out_error_capacity = buf.capacity() as u64; + *out_error_len = buf.len() as u64; + *out_error_data = buf.data_pointer() as *mut u8; + } + std::mem::forget(buf); + } + } +} +``` + +### 3.2 完整 FFI 函数 + +```rust +#[no_mangle] +pub extern "C" fn ffi_my_function_separate( + // Input: raw bytes (len + data ptr) + input_len: i32, + input_data: *const u8, + // Output: result (out-pointers for capacity/len/data) + out_result_cap: *mut u64, + out_result_len: *mut u64, + out_result_data: *mut *mut u8, + // Output: error (out-pointers) + out_error_cap: *mut u64, + out_error_len: *mut u64, + out_error_data: *mut *mut u8, +) { + // 1. Deserialize input + let input = unsafe { std::slice::from_raw_parts(input_data, input_len as usize) }; + let args: MyArgs = match serde_json::from_slice(input) { + Ok(a) => a, + Err(_) => { /* error handling */ return; } + }; + + // 2. Execute logic + let result = my_core_function(args); + + // 3. Serialize via out-pointers + write_result_to_out_ptrs( + result.map(|v| serde_json::to_vec(&v).unwrap_or_default()), + out_result_cap, out_result_len, out_result_data, + out_error_cap, out_error_len, out_error_data, + ); +} +``` + +--- + +## 4. 内存管理 + +### 4.1 所有权 + +| 参数方向 | 所有权 | 释放责任 | +|----------|:--:|------| +| Input data | 借用(caller 保留) | Caller | +| Output data | 转移给 caller | Caller 调用 `rustbuffer_free_separate` | + +### 4.2 释放函数 + +```rust +#[no_mangle] +pub extern "C" fn ffi_rustbuffer_free_separate( + capacity: u64, + len: u64, + data: *mut u8, +) { + if data.is_null() { return; } + unsafe { + let _v = Vec::from_raw_parts(data, len as usize, capacity as usize); + // Drop here + } +} +``` + +**Swift 调用示例**: +```swift +var resultCap: UInt64 = 0, resultLen: UInt64 = 0, resultData: UnsafeMutablePointer? +var errorCap: UInt64 = 0, errorLen: UInt64 = 0, errorData: UnsafeMutablePointer? + +ffi_parse_markdown_separate( + Int32(jsonData.count), pointerToData, + &resultCap, &resultLen, &resultData, + &errorCap, &errorLen, &errorData +) + +// 使用 result... +if let data = resultData { + let buf = Data(bytes: data, count: Int(resultLen)) + // ... parse JSON +} + +// 释放 +ffi_rustbuffer_free_separate(resultCap, resultLen, resultData) +ffi_rustbuffer_free_separate(errorCap, errorLen, errorData) +``` + +--- + +## 5. 设计原则 + +### 5.1 标量仅传 + +- ✅ `i32`, `u64`, `*mut u8` — 单寄存器传参 +- ❌ `RustBuffer { capacity, len, data }` — 多字段 struct + +### 5.2 成对输出 + +每个输出 buffer 拆为 3 个 out-pointer: +``` +capacity: *mut u64 → buffer 容量 +len: *mut u64 → 有效数据长度 +data: *mut *mut u8 → 数据指针(双重指针:写入指针值) +``` + +### 5.3 序列化格式 + +输入/输出均使用 JSON(`serde_json`)序列化,因为: +- C-ABI 仅传递字节数组 +- JSON 在 Swift 侧有原生支持(`JSONDecoder`) +- 避免复杂 struct 布局的 ABI 问题 + +--- + +## 6. 当前使用范围 + +项目中有以下函数使用 out-pointer 模式(`_separate` 后缀): + +| 函数 | 输入 | 输出 | 状态 | +|------|:--:|:--:|:--:| +| `parse_markdown_separate` | JSON args | `Vec` | ✅ 已实现 | +| `parse_text_separate` | JSON args | `Vec` | ✅ 已实现 | +| `detect_material_type_separate` | 文件路径 | `MaterialType` | ✅ 已实现 | +| `read_image_meta_separate` | 文件路径 | `ImageMeta` | ✅ 已实现 | +| `read_text_stats_separate` | 文件路径 | `TextStats` | ✅ 已实现 | +| `search_markdown_blocks_separate` | JSON args | `Vec` | ✅ 已实现 | +| `search_text_content_separate` | JSON args | `Vec` | ✅ 已实现 | +| `search_pdf_pages_separate` | JSON args | `Vec` | ✅ 已实现 | +| `push_reading_event_separate` | JSON event | () | ✅ 已实现 | +| `export_pending_events_separate` | () | `Vec` | ✅ 已实现 | + +--- + +## 7. 何时使用 Out-Pointer + +| 场景 | 推荐方式 | +|------|----------| +| V2 Session / Event(新接口) | Standard UniFFI(已验证稳定) | +| 返回复杂类型(Vec/struct)且有 ARM64 兼容需求 | Out-pointer + JSON | +| 简单标量输入输出 | Standard UniFFI | +| 需要高性能避免 JSON 开销 | Standard UniFFI + 测试验证 | + +--- + +## 8. 相关文档 + +- [UniFFI UDL 编写规范](./uniffi-udl-spec.md) +- [iOS FFI 调用指南](./ios-ffi-integration-guide.md)