- 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>
240 lines
7.1 KiB
Markdown
240 lines
7.1 KiB
Markdown
# 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<Vec<DocumentBlock>, 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<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 函数
|
||
|
||
```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<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` 后缀):
|
||
|
||
| 函数 | 输入 | 输出 | 状态 |
|
||
|------|:--:|:--:|:--:|
|
||
| `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. 相关文档
|
||
|
||
- [UniFFI UDL 编写规范](./uniffi-udl-spec.md)
|
||
- [iOS FFI 调用指南](./ios-ffi-integration-guide.md)
|