mcpField
D
dart-use-ffigen

dart-use-ffigen

github.com/flutter開発3.1k14.4k 回インストール2 日前に更新
リポジトリ

dart-use-ffigen について

ネイティブヘッダーから `package:ffigen` で FFI バインディングを生成する、Dart 開発者向けガイドです。ジェネレーター設定、ファイル構成、シンボルの絞り込み、生成コードの静的解析を扱います。

主な機能

  1. 01ヘッダーから Dart FFI バインディングを生成
  2. 02関数とヘッダーのフィルター設定
  3. 03生成スクリプトと出力先の整理
  4. 04使用記録マッピングの生成
  5. 05生成バインディングの静的解析

活用シーン

  1. 01C ライブラリ向け Dart バインディングの作成
  2. 02手書きのネイティブバインディングの置き換え
  3. 03バインディング生成をプロジェクトの手順に追加
  4. 04生成コードと解析結果の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/flutter/agent-plugins --skill dart-use-ffigen

インストール後、Claude Code で/dart-use-ffigenを入力して読み込まれたか確認します。

詳細

package:ffigen を使用した FFI バインディングの生成

目次

はじめに

package:ffigen(FfiGenerator)を使用して、FFI バインディングの生成を自動化し、標準化します。FFI バインディングを手作業で記述すると、エラーが発生しやすく、脆弱であるため、強く推奨されません。

制約

  • 手書きの FFI バインディングは禁止:ネイティブヘッダー(.h ファイル)が存在する場合、またはビルド手順で生成される場合、手動で DynamicLibrary.lookup、@Native 外部関数、または生の構造体クラスを記述してはいけません。必ず FfiGenerator を使用して生成してください。
  • ジェネレーターの配置場所:ジェネレータースクリプトは、対象パッケージのルート内にある tool/ffigen.dart に配置してください。
  • ヘッダーの配置場所:ネイティブヘッダーファイルがサードパーティ製の場合、対象パッケージ内の third_party/ に配置してください(それ以外の場合は、パッケージルートの src/ ディレクトリに配置してもかまいません)。ヘッダーがこれらの標準的な場所にない場合は、ヘッダーファイルを標準的な場所(例:third_party/)に移動するとより整理しやすいことをユーザーに伝えてください。
  • 対象を絞った包含フィルター:特に必要な場合を除き、ネイティブライブラリ全体をインポートしないでください。生成コードのサイズと認知負荷を最小限に抑えるため、肯定的な一致条件を用いた正確な包含リストを必ず適用してください(例:Functions.includeSet を使用する、または include クロージャー内で一致条件をフィルタリングする)。
  • 出力の設定:生成された FFI バインディングがサードパーティライブラリと連携する場合(またはサードパーティヘッダーを参照する場合)、生成ファイルは必ず lib/src/third_party/ 配下に配置してください。主要な生成 FFI バインディングファイルの拡張子には、必ず .g.dart を使用してください(例:sqlite3.g.dart)。
  • 前文とライセンスヘッダー:ライセンスを指定するため、Output クラスで必ず適切な preamble を指定してください。これはネイティブサードパーティライブラリのライセンスと一致させ、対象となるネイティブヘッダーファイルの著作権表示を明示的に含め、自動生成に関する警告(例:// Generated by package:ffigen. Do not edit manually.)を含める必要があります。
  • 古いバインディングの不要なコミットを避ける:作業を終える前にジェネレータースクリプトを実行し、生成ファイルに変更があったか確認してください。必ず dart analyze を実行してパッケージを検証してください。
  • 使用状況の記録とツリーシェイキング:パッケージが標準のランタイム実行に組み込まれている場合、またはネイティブフックを介してネイティブアセットをコンパイルする場合:
    • Functions の下に recordUse: (_) => true を設定し、すべての関数で使用状況の記録を有効にしてください。
    • Output で recordUseMapping の出力先を指定し、シンボルのツリーシェイキング用にバインディングを登録してください(出力先には必ず lib/src/third_party/ 配下の .g.dart ファイルを指定します。例:lib/src/third_party/sqlite3.record_use_mapping.g.dart)。

FFIgen の概要

プログラムによるジェネレーターを構築するには、package:ffigen/ffigen.dart からインポートする中核的な設定オブジェクトを使用します:

1. FfiGenerator

設定、解析、コード生成を統括する親クラスです。

FfiGenerator({
  Headers headers = const Headers(),
  Enums enums = Enums.excludeAll,
  Functions functions = Functions.excludeAll,
  Globals globals = Globals.excludeAll,
  Integers integers = const Integers(),
  Macros macros = Macros.excludeAll,
  Structs structs = Structs.excludeAll,
  Typedefs typedefs = Typedefs.excludeAll,
  Unions unions = Unions.excludeAll,
  UnnamedEnums unnamedEnums = UnnamedEnums.excludeAll,
  ObjectiveC? objectiveC,
  required Output output,
}).generate();
2. Headers

Clang ヘッダーの解析対象とコンパイラーフラグを設定します。

  • entryPoints:対象ヘッダーの Uri 入力のリストです。
  • include:推移的なヘッダーインポートを処理するフィルター関数 bool Function(Uri header) です。
  • compilerOptions:libclang に直接渡す、カスタムのプリプロセッサー/インクルード用コンパイラーフラグです。
  • ignoreSourceErrors:解析中にサードパーティヘッダー内で発生するエラーを無視するには、true に設定します。
3. Functions

Dart に公開するネイティブ C/C++ 関数を指定します。

  • include: マッチャー関数です(例:(decl) => {'my_func'}.contains(decl.originalName) または Functions.includeSet({'my_func'}))。
  • isLeaf: Dart へのコールバックやスレッド実行のブロックを行わない関数をリーフ関数として宣言します((decl) => true)。
  • recordUse: ネイティブアセットツリーシェイキング用のメタデータ生成を有効にします(dart-lang/native では必須です)。(_) => true を設定します。
4. Output

生成するファイルの出力先を設定します。

  • dartFile: プライマリ FFI バインディングを書き込む出力先の Uri です。
  • recordUseMapping: 記録された使用状況メタデータマップの出力先の Uri です(リンク時のツリーシェイキングに不可欠です)。
  • preamble: 生成ファイルの先頭に挿入するテキストです(ライセンス、アノテーションなど)。
  • format: true に設定すると、Dart フォーマッターを自動的に実行します。

手順ごとのワークフロー

Step 1: 依存関係の確認・追加

パッケージの pubspec.yaml を開き、dev_dependencies に ffigen が含まれていることを確認します。Dart MCP サーバーを使用するか、pub.dev で最新バージョン(例:^20.1.1)を確認してください。

CLI を使って自動的に追加できます:

dart pub add dev:ffigen
Step 2: パスを動的に組み立てる

パッケージの tool/ ディレクトリに、プログラムによるジェネレータースクリプトを作成します(例:tool/ffigen.dart)。 Platform.script を基準にパスを解決し、どの作業ディレクトリからでも正常に実行できるようにします:

final packageRoot = Platform.script.resolve('../');
final headerFile = packageRoot.resolve('third_party/library.h');
final targetBindings = packageRoot.resolve('lib/src/third_party/bindings.g.dart');
Step 3: スクリプトを作成する(tool/ffigen.dart)

void main() を定義し、動的なオプションを指定して FfiGenerator を実行します(以下の完全な例を参照してください)。

Step 4: コード生成を実行する

対象パッケージのフォルダー内で、ターミナルからスクリプトを実行します:

dart run tool/ffigen.dart
Step 5: 静的解析

生成されたバインディングが正しいことを確認し、解析上の問題を解決します。FFIgen は format: true の設定によって、出力ファイルに対して Dart フォーマッターを自動的に実行するため、手動でのフォーマットは不要です。

  1. 対象パッケージ内で静的アナライザーを実行します:
    dart analyze
    
  2. 警告/Lint への対処:dart analyze が生成ファイル内でスタイルまたは lint の警告を報告した場合は、対応する警告コードをジェネレータースクリプトの preamble 設定にある ignore_for_file: リストへ追加します(例:camel_case_types、non_constant_identifier_names など)。パッケージ全体のルールは変更しないでください。
  3. コンパイルエラーへの対処:dart analyze が生成ファイル内で実際のコンパイラーエラーまたは解析エラー(警告ではないもの)を報告した場合、生成ファイルを手動で編集しないでください。エラーの詳細をユーザーに直接報告し、リポジトリの github.com/dart-lang/native に issue を作成できるようにしてください。

具体例:C ライブラリのバインディング

FFI を介してアクセスする SQLite の C ライブラリソースを third_party/sqlite/ に組み込んだ pkgs/code_assets/example/sqlite パッケージを扱うとします。

C ヘッダーファイル(third_party/sqlite/sqlite3.h)
// The author disclaims copyright to this source code.

#ifndef SQLITE3_H_
#define SQLITE3_H_

const char *sqlite3_libversion(void);

#endif // SQLITE3_H_
BEFORE:手動による FFI バインディング(アンチパターン)

開発者がこの連携を手作業で作成しようとする場合があります。これは壊れやすく、ツリーシェイキング用メタデータを使用できなくするうえ、ABI や構造体のマッピングに関する問題が非常に発生しやすくなります:

// lib/src/sqlite3_manual.dart
import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';

// Flaw 1: Hardcoded DynamicLibrary lookup blocks integration with modern native asset compilation.
final ffi.DynamicLibrary _dylib = ffi.DynamicLibrary.open('libsqlite3.so');

// Flaw 2: Manual function type matching requires writing redundant dynamic lookup boilerplate and lacks tree-shaking metadata.
typedef _sqlite3_libversion_C = ffi.Pointer<ffi.Char> Function();
typedef _sqlite3_libversion_Dart = ffi.Pointer<ffi.Char> Function();

final _sqlite3_libversion_Dart sqlite3LibVersion = _dylib
    .lookup<ffi.NativeFunction<_sqlite3_libversion_C>>('sqlite3_libversion')
    .asFunction();
AFTER:FFIgen による生成(推奨パターン)

tool/ffigen.dart にプログラムによるスクリプトを作成します:

// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';
import 'package:ffigen/ffigen.dart';

void main() {
  // Resolve paths dynamically relative to Platform.script
  final packageRoot = Platform.script.resolve('../');
  final entryHeader = packageRoot.resolve('third_party/sqlite/sqlite3.h');
  final bindingsOutput = packageRoot.resolve('lib/src/third_party/sqlite3.g.dart');
  final treeShakeMapping = packageRoot.resolve('lib/src/third_party/sqlite3.record_use_mapping.g.dart');

  FfiGenerator(
    headers: Headers(
      entryPoints: [entryHeader],
    ),
    functions: Functions(
      include: (decl) => {'sqlite3_libversion'}.contains(decl.originalName),
      // Essential for package optimization and tree-shaking
      recordUse: (_) => true,
    ),
    output: Output(
      dartFile: bindingsOutput,
      recordUseMapping: treeShakeMapping,
      format: true,
      preamble: '''

// AUTO-GENERATED FILE - DO NOT MODIFY.
// Generated via ffigen.
// To regenerate: dart run tool/ffigen.dart

// ignore_for_file: type=lint, unused_import, unused_element, deprecated_member_use_from_same_package, experimental_member_use
''',
    ),
  ).generate();

  print('Successfully generated sqlite3 FFI bindings.');
}
ジェネレータースクリプトの実行

パッケージのルートディレクトリで次を実行します:

dart run tool/ffigen.dart

これにより、次のファイルが自動的に作成されます:

  1. lib/src/third_party/sqlite3.g.dart
  2. lib/src/third_party/sqlite3.record_use_mapping.g.dart

検証チェックリスト

バインディング生成タスクを完了する前に、必ず以下を検証してください。

  1. 正しいセットアップ:生成されたファイルの配置先が lib/src/third_party/ 内であること(サードパーティライセンスのコードでは必須)と、主要な FFI バインディングファイルの拡張子が .g.dart であることを確認します。
  2. 静的解析:dart analyze を実行し、パッケージ内にコンパイラーまたはアナライザーのエラーや警告がないことを確認します。
    • 生成されたバインディングで静的解析の警告が報告された場合は、グローバルなパッケージルールを変更せず、ジェネレーターの preamble 設定に ignore_for_file ルールを追加して抑制します。
    • 生成されたバインディングで実際のコンパイラーまたはアナライザーのエラーが報告された場合は、生成ファイルを手動で編集しないでください。詳細をユーザーに報告し、github.com/dart-lang/native にイシューを作成するよう案内してください。

コメント