Some checks failed
CI/CD Pipeline / Lint (push) Has been cancelled
CI/CD Pipeline / Test (push) Has been cancelled
CI/CD Pipeline / Build (push) Has been cancelled
CI/CD Pipeline / Release (macos-15, aarch64-apple-ios, aarch64-apple-ios) (push) Has been cancelled
CI/CD Pipeline / Release (macos-15, aarch64-apple-ios-sim, aarch64-apple-ios-sim) (push) Has been cancelled
CI/CD Pipeline / Package xcframework (push) Has been cancelled
- Add missing: build_document_info, search_epub, update_position, create_note_anchor, create_note_anchor_from_search - Add memory management functions (rustbuffer_free/from_bytes) - Organize by category: memory, parse, search, events, anchors Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.7 KiB
7.7 KiB
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 序列化格式
输入/输出均使用 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<DocumentBlock> |
parse_text_separate |
JSON args | Vec<DocumentBlock> |
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<SearchResult> |
search_text_content_separate |
JSON args | Vec<SearchResult> |
search_pdf_pages_separate |
JSON args | Vec<SearchResult> |
search_epub_chapters_ffi_separate |
JSON args | Vec<SearchResult> |
V1 事件(deprecated)
| 函数 | 输入 | 输出 |
|---|---|---|
push_reading_event_separate |
JSON event | () |
update_reading_position_separate |
JSON args | () |
export_pending_events_separate |
() | Vec<ReadingEvent> |
锚点
| 函数 | 输入 | 输出 |
|---|---|---|
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 + 测试验证 |