# 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` 后缀): ### 内存管理 | 函数 | 说明 | |------|------| | `rustbuffer_free_separate` | 释放 out-pointer 分配的 buffer | | `rustbuffer_from_bytes_separate` | 从 raw bytes 构造 RustBuffer | ### 文档解析 | 函数 | 输入 | 输出 | |------|:--:|:--:| | `build_document_info_separate` | JSON args | `DocumentInfo` | | `parse_markdown_separate` | JSON args | `Vec` | | `parse_text_separate` | JSON args | `Vec` | | `detect_material_type_separate` | String | `MaterialType` | | `read_image_meta_separate` | String | `ImageMeta` | | `read_text_stats_separate` | String | `TextStats` | ### 搜索 | 函数 | 输入 | 输出 | |------|:--:|:--:| | `search_markdown_blocks_separate` | JSON args | `Vec` | | `search_text_content_separate` | JSON args | `Vec` | | `search_pdf_pages_separate` | JSON args | `Vec` | | `search_epub_chapters_ffi_separate` | JSON args | `Vec` | ### V1 事件(deprecated) | 函数 | 输入 | 输出 | |------|:--:|:--:| | `push_reading_event_separate` | JSON event | () | | `update_reading_position_separate` | JSON args | () | | `export_pending_events_separate` | () | `Vec` | ### 锚点 | 函数 | 输入 | 输出 | |------|:--:|:--:| | `create_note_anchor_separate` | JSON args | `NoteAnchor` | | `create_note_anchor_from_search_separate` | JSON args | `NoteAnchor` | **总计**:17 个 `_separate` 函数(含 2 个内存管理 + 15 个业务函数) --- ## 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)