From Dart and Flutter
Automates FFI binding generation in Dart using package:ffigen. Replaces manual dart:ffi code when integrating C, Objective-C, or Swift native libraries.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dart-flutter:dart-use-ffigenThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- [Introduction](#introduction)
Automate and standardize the generation of FFI bindings using package:ffigen (FfiGenerator). Writing FFI bindings by hand is error-prone, brittle, and highly discouraged.
.h files) exist or are generated by a build step, never write manual DynamicLibrary.lookup, @Native external functions, or raw struct classes. Always use FfiGenerator to generate them.tool/ffigen.dart within the target package root.third_party/ within the target package (otherwise placing them in a src/ directory at the package root is also acceptable). If the headers are not in one of these standard locations, notify the user that it would be cleaner to move the header files to the standard location (e.g., third_party/).Functions.includeSet or filtering matches in include closures).lib/src/third_party/. The primary generated FFI bindings file must strictly use the .g.dart extension (e.g. sqlite3.g.dart).preamble in the Output class to specify the license. This must match the native third-party library's license, explicitly include the copyright header of the target native header file, and contain an automatic generation warning (e.g. // Generated by package:ffigen. Do not edit manually.).dart analyze.recordUse: (_) => true under Functions.recordUseMapping target in Output (which must strictly be a .g.dart file under lib/src/third_party/, e.g. lib/src/third_party/sqlite3.record_use_mapping.g.dart) to register bindings for symbol tree shaking.To construct the programmatic generator, use the core configuration objects imported from package:ffigen/ffigen.dart:
FfiGeneratorThe parent class that orchestrates the configuration, parsing, and code generation.
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();
HeadersConfigures Clang header parsing targets and compiler flags.
entryPoints: A list of target header Uri inputs.include: A filter function bool Function(Uri header) that handles transitive header imports.compilerOptions: Custom preprocessor/include compiler flags to pass directly to libclang.ignoreSourceErrors: Set to true to silence errors occurring inside third-party headers during parsing.FunctionsSpecifies which native C/C++ functions to expose in Dart.
include: A matcher function (e.g. (decl) => {'my_func'}.contains(decl.originalName) or Functions.includeSet({'my_func'})).isLeaf: Declares functions as leaf functions ((decl) => true) if they do not call back into Dart or block thread execution.recordUse: Enables metadata generation for native asset tree shaking (essential in dart-lang/native). Set to (_) => true.OutputConfigures target generated files.
dartFile: Target Uri where the primary FFI bindings will be written.recordUseMapping: Target Uri for recorded usage metadata maps (crucial for linking-time tree shaking).preamble: Text inserted at the top of the generated file (licensing, annotations).format: Set to true to run the Dart formatter automatically.Open the package's pubspec.yaml and verify the dev_dependencies contains ffigen. Use the Dart MCP server or look up the latest version on pub.dev (e.g., ^20.1.1).
You can add it automatically using the CLI:
dart pub add dev:ffigen
Create a programmatic generator script under the package's tool/ directory (e.g., tool/ffigen.dart).
Resolve paths relative to Platform.script to make sure it runs successfully from any working directory:
final packageRoot = Platform.script.resolve('../');
final headerFile = packageRoot.resolve('third_party/library.h');
final targetBindings = packageRoot.resolve('lib/src/third_party/bindings.g.dart');
tool/ffigen.dart)Define void main() and run FfiGenerator with dynamic options (see complete example below).
Execute the script from the terminal inside the target package folder:
dart run tool/ffigen.dart
Verify that the generated bindings are correct and resolve any analysis issues. FFIgen automatically runs the Dart formatter on the output file (via format: true configuration), so manual formatting is not required.
dart analyze
dart analyze reports style or lint warnings inside the generated file, append the corresponding warning codes to the ignore_for_file: list in your generator script's preamble configuration (e.g., adding camel_case_types, non_constant_identifier_names, etc.). Do not modify the package's global rules.dart analyze reports actual compiler or analysis errors (not warnings) inside the generated file, do not attempt to edit the generated file manually. Report these error details directly to the user so they can file an issue on the repository at github.com/dart-lang/native.Let's assume we are working with the SQLite package under pkgs/code_assets/example/sqlite, which embeds SQLite C library sources inside third_party/sqlite/ and accesses it via FFI.
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_
A developer might attempt to handcraft this integration. It is fragile, blocks tree-shaking metadata, and is highly prone to ABI and structural mapping issues:
// 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();
Create a programmatic script at 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.');
}
Run this in the package root directory:
dart run tool/ffigen.dart
This will automatically create:
lib/src/third_party/sqlite3.g.dartlib/src/third_party/sqlite3.record_use_mapping.g.dartAlways perform the following verification before completing a binding generation task:
lib/src/third_party/ (required for third-party licensed code) and the primary FFI bindings file strictly uses the .g.dart extension.dart analyze and ensure there are zero compiler/analyzer errors or warnings in the package.
ignore_for_file rules to the generator's preamble configuration (do not modify global package rules).npx claudepluginhub flutter/agent-plugins --plugin dart-flutterGuides compiling/packaging C/C++ into Dart/Flutter native code assets via hook/build.dart and hook/link.dart. Use for native interop or bundling dynamic libraries.
End-to-end workflow for creating maintainable MoonBit FFI bindings for C/C++ libraries, from upstream survey to vendoring, safe API design, tests, and ASan validation.
Reviews Rust FFI code for type safety, memory layout, string handling, callbacks, and unsafe boundary correctness. Use for extern blocks, #[repr(C)], bindgen, or C/C++ interop.