dart-use-ffigen

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Generating FFI Bindings using package:ffigen

使用package:ffigen生成FFI绑定

Contents

目录

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.
使用
package:ffigen
(即
FfiGenerator
)自动化并标准化FFI绑定的生成。手动编写FFI绑定容易出错、稳定性差,强烈不建议这么做。

Constraints

约束条件

  • No Hand-Written FFI Bindings: If native headers (
    .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.
  • Generator Location: The generator script should be located at
    tool/ffigen.dart
    within the target package root.
  • Header Location: If the native header files are third-party, they should be located in
    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/
    ).
  • Targeted Inclusion Filters: Avoid importing an entire native library unless specifically needed. Always apply precise inclusion lists using positive matches to minimize the size and cognitive load of the generated code (e.g., using
    Functions.includeSet
    or filtering matches in
    include
    closures).
  • Output Setup: If the generated FFI bindings interface with a third-party library (or reference third-party headers), the generated files must always be placed under
    lib/src/third_party/
    . The primary generated FFI bindings file must strictly use the
    .g.dart
    extension (e.g.
    sqlite3.g.dart
    ).
  • Preamble & License Headers: Always supply a premium
    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.
    ).
  • No Unnecessary Commits of Stale Bindings: Ensure you run the generator script and check if the generated files have changed before finishing your task. Always verify the package by running
    dart analyze
    .
  • Record Usage and Tree Shaking: If the package is integrated into standard runtime execution or compiles native assets via native hooks:
    • Enable recorded usage on all functions by setting
      recordUse: (_) => true
      under
      Functions
      .
    • Specify the
      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.
  • 禁止手动编写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 Overview

FFIgen概述

To construct the programmatic generator, use the core configuration objects imported from
package:ffigen/ffigen.dart
:
要构建程序化生成器,请使用从
package:ffigen/ffigen.dart
导入的核心配置对象:

1.
FfiGenerator

1.
FfiGenerator

The parent class that orchestrates the configuration, parsing, and code generation.
dart
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();
协调配置、解析和代码生成的父类。
dart
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

2.
Headers

Configures 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.
配置Clang头文件解析目标和编译器标志。
  • entryPoints
    :目标头文件
    Uri
    输入列表。
  • include
    :处理传递性头文件导入的过滤函数
    bool Function(Uri header)
  • compilerOptions
    :直接传递给libclang的自定义预处理器/包含编译器标志。
  • ignoreSourceErrors
    :设置为
    true
    以静默解析第三方头文件时出现的错误。

3.
Functions

3.
Functions

Specifies 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
    .
指定要在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

4.
Output

Configures 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.
配置目标生成文件。
  • dartFile
    :主FFI绑定将写入的目标
    Uri
  • recordUseMapping
    :记录使用情况元数据映射的目标
    Uri
    (对链接时摇树优化至关重要)。
  • preamble
    :插入到生成文件顶部的文本(许可证、注释)。
  • format
    :设置为
    true
    以自动运行Dart格式化工具。

Step-by-Step Workflow

分步工作流程

Step 1: Check/Add Dependencies

步骤1:检查/添加依赖

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:
bash
dart pub add dev:ffigen
打开包的
pubspec.yaml
,验证
dev_dependencies
中是否包含
ffigen
。使用Dart MCP服务器或在pub.dev上查找最新版本(例如
^20.1.1
)。
你可以通过CLI自动添加:
bash
dart pub add dev:ffigen

Step 2: Formulate Paths Dynamically

步骤2:动态制定路径

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:
dart
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/
目录下创建程序化生成器脚本(例如
tool/ffigen.dart
)。 相对于
Platform.script
解析路径,确保脚本可从任何工作目录成功运行:
dart
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: Write the Script (
tool/ffigen.dart
)

步骤3:编写脚本(
tool/ffigen.dart

Define
void main()
and run
FfiGenerator
with dynamic options (see complete example below).
定义
void main()
并使用动态选项运行
FfiGenerator
(见下方完整示例)。

Step 4: Run Code Generation

步骤4:运行代码生成

Execute the script from the terminal inside the target package folder:
bash
dart run tool/ffigen.dart
在目标包文件夹内的终端中执行脚本:
bash
dart run tool/ffigen.dart

Step 5: Static Analysis

步骤5:静态分析

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.
  1. Run the static analyzer inside the target package:
    bash
    dart analyze
  2. Addressing Warnings/Lints: If
    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.
  3. Addressing Compilation Errors: If
    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.
验证生成的绑定是否正确,并解决任何分析问题。FFIgen会自动对输出文件运行Dart格式化工具(通过
format: true
配置),因此无需手动格式化。
  1. 在目标包内运行静态分析器:
    bash
    undefined
dart analyze
2.  **处理警告/代码规范问题**:如果`dart analyze`在生成文件中报告样式或代码规范警告,将相应的警告代码添加到生成器脚本`preamble`配置的`ignore_for_file:`列表中(例如添加`camel_case_types`、`non_constant_identifier_names`等)。不要修改包的全局规则。
3.  **处理编译错误**:如果`dart analyze`在生成文件中报告实际的编译器或分析错误(而非警告),不要尝试手动编辑生成文件。直接向用户报告这些错误详情,以便他们在[github.com/dart-lang/native](https://github.com/dart-lang/native)仓库提交问题。

Concrete Example: Binding a C Library

具体示例:绑定C库

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.
假设我们正在处理
pkgs/code_assets/example/sqlite
下的SQLite包,该包将SQLite C库源码嵌入在
third_party/sqlite/
中,并通过FFI访问它。

The C Header File (
third_party/sqlite/sqlite3.h
)

C头文件(
third_party/sqlite/sqlite3.h

c
// The author disclaims copyright to this source code.  In place of
// a legal notice, here is a blessing:
//
//    May you do good and not evil.
//    May you find forgiveness for yourself and forgive others.
//    May you share freely, never taking more than you give.

#ifndef SQLITE3_H_
#define SQLITE3_H_

const char *sqlite3_libversion(void);

#endif // SQLITE3_H_
c
// The author disclaims copyright to this source code.  In place of
// a legal notice, here is a blessing:
//
//    May you do good and not evil.
//    May you find forgiveness for yourself and forgive others.
//    May you share freely, never taking more than you give.

#ifndef SQLITE3_H_
#define SQLITE3_H_

const char *sqlite3_libversion(void);

#endif // SQLITE3_H_

BEFORE: Manual FFI Binding (The Anti-Pattern)

之前:手动FFI绑定(反模式)

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:
dart
// 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();
开发者可能会尝试手工编写此集成。这种方式稳定性差,会阻碍摇树优化元数据,且极易出现ABI和结构映射问题:
dart
// lib/src/sqlite3_manual.dart
import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';

// 缺陷1:硬编码的DynamicLibrary查找会阻碍与现代原生资产编译的集成。
final ffi.DynamicLibrary _dylib = ffi.DynamicLibrary.open('libsqlite3.so');

// 缺陷2:手动函数类型匹配需要编写冗余的动态查找样板代码,且缺少摇树优化元数据。
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: Generating via FFIgen (The Correct Pattern)

之后:通过FFIgen生成(正确模式)

Create a programmatic script at
tool/ffigen.dart
:
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: '''
// The author disclaims copyright to this source code.  In place of
// a legal notice, here is a blessing:
//
//    May you do good and not evil.
//    May you find forgiveness for yourself and forgive others.
//    May you share freely, never taking more than you give.

// 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.');
}
tool/ffigen.dart
创建程序化脚本:
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() {
  // 相对于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),
      // 对包优化和摇树优化至关重要
      recordUse: (_) => true,
    ),
    output: Output(
      dartFile: bindingsOutput,
      recordUseMapping: treeShakeMapping,
      format: true,
      preamble: '''
// The author disclaims copyright to this source code.  In place of
// a legal notice, here is a blessing:
//
//    May you do good and not evil.
//    May you find forgiveness for yourself and forgive others.
//    May you share freely, never taking more than you give.

// 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.');
}

Running the Generator script

运行生成器脚本

Run this in the package root directory:
bash
dart run tool/ffigen.dart
This will automatically create:
  1. lib/src/third_party/sqlite3.g.dart
  2. lib/src/third_party/sqlite3.record_use_mapping.g.dart
在包根目录运行:
bash
dart run tool/ffigen.dart
这将自动创建:
  1. lib/src/third_party/sqlite3.g.dart
  2. lib/src/third_party/sqlite3.record_use_mapping.g.dart

Verification Checklist

验证清单

Always perform the following verification before completing a binding generation task:
  1. Correct Setup: Verify the target generated files are inside
    lib/src/third_party/
    (required for third-party licensed code) and the primary FFI bindings file strictly uses the
    .g.dart
    extension.
  2. Static Analysis: Run
    dart analyze
    and ensure there are zero compiler/analyzer errors or warnings in the package.
    • If static warnings are reported in the generated bindings, suppress them by adding
      ignore_for_file
      rules to the generator's
      preamble
      configuration (do not modify global package rules).
    • If actual compiler or analyzer errors are reported in the generated bindings, do not edit the generated file manually. Report the details to the user and direct them to file an issue at github.com/dart-lang/native.
在完成绑定生成任务前,务必执行以下验证:
  1. 正确设置:验证目标生成文件是否在
    lib/src/third_party/
    内(第三方许可代码的要求),且主FFI绑定文件严格使用
    .g.dart
    扩展名。
  2. 静态分析:运行
    dart analyze
    ,确保包中没有编译器/分析器错误或警告。
    • 如果生成的绑定中出现静态警告,通过在生成器的
      preamble
      配置中添加
      ignore_for_file
      规则来抑制(不要修改包的全局规则)。
    • 如果生成的绑定中出现实际的编译器或分析器错误,不要手动编辑生成文件。向用户报告详情,并引导他们在github.com/dart-lang/native提交问题。