Appearance
dart-ffi-demo
1. 实验目标
用最小 Dart CLI 验证 dart:ffi 零拷贝与原生函数调用(对应 docs/05-bridge-hybrid/06):
DynamicLibrary.process()拿进程符号表,lookupFunction绑定 C 函数NativeFunction/Pointer/Int32/IntPtr/Void类型映射malloc+asTypedList:Dart 直接读写 Native 内存(零拷贝)- 对照:FFI 无序列化开销,而 PlatformChannel 每次调用都要内存序列化 + 切主线程
2. 工程要点
- 纯
dart:ffi(无package:ffi依赖),macOS / Linux 直接可跑 - C 函数签名声明(
typedef ... Native/Dart成对)是 FFI 绑定核心 sizeOf<IntPtr>()获取原生指针尺寸;asTypedList(n)把 Native 内存包装成 Dart 视图- 手写字符串时注意 null terminator;本例用 malloc 分配数值数组简化
3. 关键实现速览
3.1 绑定 C 函数
为什么看这段:Dart 调用 C 函数必须声明签名对(Native 类型 → Dart 类型)。
dart
typedef CGetpidNative = Int32 Function();
typedef CGetpidDart = int Function();
final libc = DynamicLibrary.process(); // 当前进程符号表
final getpid = libc.lookupFunction<CGetpidNative, CGetpidDart>('getpid');
print('getpid() = ${getpid()}'); // 直接调用3.2 零拷贝读写 Native 内存
为什么看这段:asTypedList 不复制数据,Dart 视图与 C 堆是同一块内存。
dart
final ptr = malloc(sizeOf<IntPtr>() * count).cast<Uint8>();
final view = ptr.asTypedList(count); // 视图,无拷贝
view[0] = 255; // Dart 写
print(ptr.asTypedList(count)[0]); // C 侧同址读到 255
free(ptr.cast()); // 必须释放4. 运行方式
bash
cd labs/dart/host
dart run bin/runtime_concurrency/ffi_demo.dart5. 预期现象
getpid() = <pid>(Dart 调用真实 C 函数)- 通过 Dart 视图写入
[0,10,...,70]并读回;改view[0]=255后同址读回 255(零拷贝证明) - 打印 C 堆指针地址(
0x...)
6. 常见误区
| 误区 | 实际情况 |
|---|---|
| FFI 和 PlatformChannel 差不多 | FFI 无序列化/无主线程限制;PlatformChannel 必须序列化 + 切主线程 |
| Pointer 用完会自动释放 | 不会——Native 内存要手动 free,否则泄漏 |
| C++ 线程可以直接改 Dart 对象 | 不行——必须 Dart_PostCObject 投递消息回 Dart 事件循环 |
7. 对应知识库文档
- 理论主文档:Native 原生线程与 Dart Isolate 桥接 (FFI 零拷贝与 NativePort)
- 前置:PlatformChannel 深度解析:MethodChannel / EventChannel / BasicMessageChannel