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. 01FFI によるネイティブ C API 呼び出し
  2. 02ネイティブメモリの割り当てと解放
  3. 03クロスプラットフォームの C 型対応
  4. 04`ffigen` によるバインディング生成
  5. 05動的ライブラリの読み込み手順

活用シーン

  1. 01Dart アプリから 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:ffi ライブラリーを使用して、Dart Native プラットフォームで実行される Dart アプリケーションからネイティブ C API を呼び出し、ネイティブメモリーを管理します。

関連スキル: メインスレッドのブロックを避けるため、負荷の高い FFI ワークロードを実行する際は dart-concurrency-isolates を参照してください。

メモリー管理とファイナライゼーション

メモリーリークを防ぐため、ネイティブメモリーを明示的に管理します。

  • 必ず行うこと:C に可変サイズのデータを渡す際は、package:ffi の calloc または malloc を使用してメモリーを手動で割り当て、解放します。
  • 必ず行うこと:Finalizable と NativeFinalizer を使用して、Dart オブジェクトがガベージコレクションされた際にネイティブリソースが自動的にクリーンアップされるようにします。
  • 絶対に行わないこと:Dart のガベージコレクターがライフサイクルを管理するオブジェクトについて、手動の free() 呼び出しだけに頼らないでください。NativeFinalizer をアタッチしてください。

クロスプラットフォームの型マッピング

メモリー破損やセグメンテーションフォールトを防ぐため、C 型を Dart FFI 型に正確にマッピングします。

  • 優先すること:C 型のサイズがアーキテクチャによって異なる場合(例:int、long、size_t)、クロスプラットフォームの型マッピングには AbiSpecificInteger のサブタイプ(例:Int、Long、Size、WChar)を使用します。
  • 固定サイズのマーカー(Int8、Int32、Float、Double)は、C ヘッダーで固定幅型(例:int32_t)が明示的に定義されている場合にのみ使用します。
  • Pointer<T> を使用して、ネイティブ C メモリー内のポインターを表します。
  • 複雑な C 構造体のスーパータイプとして Struct と Union を使用します。

ワークフロー:ffigen によるバインディングの生成

大規模な API を対象とする FFI バインディングの生成を自動化するには、必ず package:ffigen を使用します。複雑なヘッダーに対して手動でバインディングを作成するのは避けてください。

タスクの進捗状況
  • 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);
}

コメント