关于 dart-native-interop-ffi
介绍 Dart 开发者如何使用 `dart:ffi` 调用原生 C API 并管理原生内存。内容涵盖类型映射、使用 `ffigen` 生成绑定、加载动态库及资源清理。
主要功能
- 01通过 FFI 调用原生 C API
- 02原生内存分配与清理
- 03跨平台 C 类型映射
- 04使用 `ffigen` 生成绑定
- 05动态库加载流程
适用场景
- 01让 Dart 应用调用 C 库
- 02为复杂头文件生成绑定
- 03管理对象生命周期内的原生资源
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dart-lang/ skills --skill dart-native-interop-ffi安装后在 Claude Code 中输入/dart-native-interop-ffi确认已加载。
详细介绍
使用 dart:ffi 与 C 互操作
目录
核心概念
在 Dart Native 平台上运行的 Dart 应用中,使用 dart:ffi 库调用本机 C API 并管理本机内存。
相关技能: 执行繁重的 FFI 工作负载时,请参阅 dart-concurrency-isolates,以避免阻塞主线程。
内存管理与终结
明确管理本机内存,以防止内存泄漏。
- 务必在向 C 传递动态大小的数据时,使用
package:ffi中的calloc或malloc手动分配和释放内存。 - 务必使用
Finalizable和NativeFinalizer,以确保在 Dart 对象被垃圾回收时自动清理本机资源。 - 切勿仅依赖手动调用
free()来管理由 Dart 垃圾回收器控制生命周期的对象。请附加NativeFinalizer。
跨平台类型映射
准确地将 C 类型映射为 Dart FFI 类型,以防止内存损坏和段错误。
- 优先使用
AbiSpecificInteger子类型(例如Int、Long、Size、WChar)进行跨平台类型映射,因为 C 类型的大小会因架构而异(例如int、long、size_t)。 - 仅当 C 头文件明确指定固定宽度类型(例如
int32_t)时,才使用固定大小标记(Int8、Int32、Float、Double)。 - 使用
Pointer<T>表示指向本机 C 内存的指针。 - 使用
Struct和Union作为复杂 C 结构的超类型。
工作流:使用 ffigen 生成绑定
务必使用 package:ffigen 自动为大型 API 集生成 FFI 绑定。避免为复杂的头文件手动编写绑定。
任务进度
- 将
ffigen添加为开发依赖项:dart pub add --dev ffigen。 - 将
ffi添加为常规依赖项:dart pub add ffi。 - 创建
ffigen.yaml配置文件(或将配置添加到pubspec.yaml),指定头文件和输出路径。 - 运行生成器:
dart run ffigen --config ffigen.yaml。 - 反馈循环: 运行
dart analyze-> 检查类型不匹配或缺少定义的错误 -> 调整ffigen.yaml(例如,添加编译器选项或包含路径)-> 重新生成。
工作流:加载并调用本机函数
根据宿主操作系统实施条件逻辑,以加载正确的动态库。
任务进度
- 使用
dart:io中的Platform确定库路径。- 如果是 macOS: 加载
.dylib。 - 如果是 Windows: 加载
.dll。 - 如果是 Linux: 加载
.so。
- 如果是 macOS: 加载
- 使用
DynamicLibrary.open(libraryPath)打开库。 - 使用 FFI 类型定义 C 函数签名(例如,
Void Function(Int32))。 - 定义 Dart 函数签名(例如,
void Function(int))。 - 查找函数:
dylib.lookup<NativeFunction<CFunc>>('symbol_name').asFunction<DartFunc>()。 - 调用得到的 Dart 函数。
示例
示例:内存分配与终结
演示分配内存、将其传递给 C,并使用 NativeFinalizer 确保清理。
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// Assume this is imported from ffigen bindings
// typedef FreeFunc = Void Function(Pointer<Void>);
// final void Function(Pointer<Void>) nativeFree = ...;
final NativeFinalizer _finalizer = NativeFinalizer(nativeFreePtr);
final Pointer<NativeFunction<Void Function(Pointer<Void>)>> nativeFreePtr = DynamicLibrary.process().lookup('free');
class NativeResource implements Finalizable {
late final Pointer<Uint8> _data;
NativeResource(int size) {
// Manually allocate memory
_data = calloc<Uint8>(size);
// Attach the finalizer to ensure cleanup when NativeResource is GC'd
_finalizer.attach(this, _data.cast<Void>(), detach: this);
}
void dispose() {
// Optional manual cleanup before GC
_finalizer.detach(this);
calloc.free(_data);
}
}
示例:加载库并调用函数
演示条件加载工作流以及特定于 ABI 的整数用法。
import 'dart:ffi' as ffi;
import 'dart:io' show Platform;
import 'package:path/path.dart' as path;
// C signature: void hello_world(size_t count);
typedef hello_world_func = ffi.Void Function(ffi.Size count);
// Dart signature
typedef HelloWorld = void Function(int count);
void main() {
final String libraryName;
if (Platform.isMacOS) {
libraryName = 'libhello.dylib';
} else if (Platform.isWindows) {
libraryName = 'hello.dll';
} else {
libraryName = 'libhello.so';
}
final libraryPath = path.join(Directory.current.path, 'hello_library', libraryName);
final dylib = ffi.DynamicLibrary.open(libraryPath);
final HelloWorld hello = dylib
.lookup<ffi.NativeFunction<hello_world_func>>('hello_world')
.asFunction();
hello(5);
}
评论
支持 Markdown