zhixi-document-runtime/docs/c-abi-out-pointer-pattern.md
wangdl f30809c5b7 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 <noreply@anthropic.com>
2026-06-18 14:43:53 +08:00

7.1 KiB
Raw Blame History

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有问题

// ❌ ARM64 不稳定RustBuffer struct 跨 FFI 传参
#[uniffi::export]
fn parse_markdown(content: String) -> Result<Vec<DocumentBlock>, DocumentError> {
    // UniFFI 自动生成 RustBuffer 序列化
}

生成的 C-ABI

// ❌ 返回 struct — ARM64 可能损坏
RustBuffer ffi_parse_markdown(RustBuffer content, RustCallStatus* status);

2.2 Out-Pointer 模式(安全)

// ✅ 所有参数/返回值均为标量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 序列化

/// Write Result<T, DocumentError> into out-pointers.
fn write_result_to_out_ptrs(
    result: Result<Vec<u8>, 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 函数

#[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 释放函数

#[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 调用示例

var resultCap: UInt64 = 0, resultLen: UInt64 = 0, resultData: UnsafeMutablePointer<UInt8>?
var errorCap: UInt64 = 0, errorLen: UInt64 = 0, errorData: UnsafeMutablePointer<UInt8>?

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 序列化格式

输入/输出均使用 JSONserde_json)序列化,因为:

  • C-ABI 仅传递字节数组
  • JSON 在 Swift 侧有原生支持(JSONDecoder
  • 避免复杂 struct 布局的 ABI 问题

6. 当前使用范围

项目中有以下函数使用 out-pointer 模式(_separate 后缀):

函数 输入 输出 状态
parse_markdown_separate JSON args Vec<DocumentBlock> 已实现
parse_text_separate JSON args Vec<DocumentBlock> 已实现
detect_material_type_separate 文件路径 MaterialType 已实现
read_image_meta_separate 文件路径 ImageMeta 已实现
read_text_stats_separate 文件路径 TextStats 已实现
search_markdown_blocks_separate JSON args Vec<SearchResult> 已实现
search_text_content_separate JSON args Vec<SearchResult> 已实现
search_pdf_pages_separate JSON args Vec<SearchResult> 已实现
push_reading_event_separate JSON event () 已实现
export_pending_events_separate () Vec<ReadingEvent> 已实现

7. 何时使用 Out-Pointer

场景 推荐方式
V2 Session / Event新接口 Standard UniFFI已验证稳定
返回复杂类型Vec/struct且有 ARM64 兼容需求 Out-pointer + JSON
简单标量输入输出 Standard UniFFI
需要高性能避免 JSON 开销 Standard UniFFI + 测试验证

8. 相关文档