mcpField
D
dart-native-interop-ffi

dart-native-interop-ffi

github.com/dart-lang开发53265 次安装2 周前更新
代码仓库

关于 dart-native-interop-ffi

介绍 Dart 开发者如何使用 `dart:ffi` 调用原生 C API 并管理原生内存。内容涵盖类型映射、使用 `ffigen` 生成绑定、加载动态库及资源清理。

主要功能

  1. 01通过 FFI 调用原生 C API
  2. 02原生内存分配与清理
  3. 03跨平台 C 类型映射
  4. 04使用 `ffigen` 生成绑定
  5. 05动态库加载流程

适用场景

  1. 01让 Dart 应用调用 C 库
  2. 02为复杂头文件生成绑定
  3. 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。
  • 使用 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