dart-native-interop-ffi について
Dart の `dart:ffi` でネイティブ C API を呼び出し、ネイティブメモリを管理する方法を解説するガイドです。型の対応付け、`ffigen` によるバインディング生成、ライブラリの読み込み、リソースの解放を扱います。
主な機能
- 01FFI によるネイティブ C API 呼び出し
- 02ネイティブメモリの割り当てと解放
- 03クロスプラットフォームの C 型対応
- 04`ffigen` によるバインディング生成
- 05動的ライブラリの読み込み手順
活用シーン
- 01Dart アプリから 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: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を読み込みます。
- 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 対応