dart-native-interop-ffi

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dart-c-interop

dart-c-interop

Goal

目标

Integrates native C libraries into Dart applications using the
dart:ffi
library. Automates FFI binding generation for large API surfaces, safely maps native types across platforms, and strictly manages native memory lifecycles using manual allocation and finalizers. Assumes the user has a configured Dart or Flutter environment and access to the target C headers and compiled dynamic libraries (
.so
,
.dylib
, or
.dll
).
使用
dart:ffi
库将原生C库集成到Dart应用中。针对大型API自动生成FFI绑定,跨平台安全映射原生类型,并通过手动分配和终结器严格管理原生内存生命周期。假设用户已配置好Dart或Flutter环境,且可访问目标C头文件和编译后的动态库(
.so
.dylib
.dll
)。

Decision Logic

决策逻辑

When implementing C interop, evaluate the API surface and memory requirements to determine the correct path:
  1. API Surface Size:
    • If the C API is large or complex: Use
      package:ffigen
      to automate binding generation.
    • If the C API is small (1-5 functions): Manually define
      typedef
      signatures and lookups.
  2. Memory Ownership:
    • If Dart creates native structures or strings: Manually allocate using
      calloc
      or
      malloc
      from
      package:ffi
      .
    • If Dart holds pointers to C-allocated memory that must be freed when the Dart object is garbage collected: Implement
      Finalizable
      and attach a
      NativeFinalizer
      .
  3. Cross-Platform Types:
    • If mapping standard C integer types (
      int
      ,
      long
      ,
      size_t
      ): PREFER
      abiSpecificInteger
      subtypes (e.g.,
      Int
      ,
      Long
      ,
      Size
      ) over fixed-width types (
      Int32
      ,
      Int64
      ) to ensure cross-platform ABI compatibility.
在实现C互操作时,需评估API规模和内存需求以确定正确方案:
  1. API规模:
    • 若C API庞大或复杂:使用
      package:ffigen
      自动生成绑定。
    • 若C API规模较小(1-5个函数):手动定义
      typedef
      签名并查找函数。
  2. 内存所有权:
    • 若Dart创建原生结构体或字符串:使用
      package:ffi
      中的
      calloc
      malloc
      手动分配内存。
    • 若Dart持有C分配的内存指针,且需在Dart对象被垃圾回收时释放:实现
      Finalizable
      并附加
      NativeFinalizer
  3. 跨平台类型:
    • 若映射标准C整数类型(
      int
      long
      size_t
      ):优先使用
      abiSpecificInteger
      子类型(如
      Int
      Long
      Size
      )而非固定宽度类型(
      Int32
      Int64
      ),以确保跨平台ABI兼容性。

Instructions

操作步骤

1. Determine Environment and Assets

1. 确认环境与资源

STOP AND ASK THE USER:
  • "What are the target platforms (macOS, Windows, Linux, Android, iOS)?"
  • "Where are the C header files (
    .h
    ) and compiled dynamic libraries located in your project?"
请询问用户以下问题:
  • "目标平台是什么(macOS、Windows、Linux、Android、iOS)?"
  • "C头文件(
    .h
    )和编译后的动态库在项目中的哪个位置?"

2. Configure Binding Generation (If using
ffigen
)

2. 配置绑定生成(若使用
ffigen

For large APIs, configure
package:ffigen
to parse C headers and generate Dart bindings. Create or update
ffigen.yaml
:
yaml
name: NativeLibrary
description: Bindings for native C library.
output: 'lib/src/generated_bindings.dart'
headers:
  entry-points:
    - 'src/native_lib.h'
  include-directives:
    - '**native_lib.h'
ffi-native:
  asset: 'native_lib'
Run the generator:
bash
dart run ffigen --config ffigen.yaml
针对大型API,配置
package:ffigen
来解析C头文件并生成Dart绑定。创建或更新
ffigen.yaml
yaml
name: NativeLibrary
description: Bindings for native C library.
output: 'lib/src/generated_bindings.dart'
headers:
  entry-points:
    - 'src/native_lib.h'
  include-directives:
    - '**native_lib.h'
ffi-native:
  asset: 'native_lib'
运行生成器:
bash
dart run ffigen --config ffigen.yaml

3. Load the Dynamic Library

3. 加载动态库

Implement platform-specific routing to load the dynamic library.
dart
import 'dart:ffi' as ffi;
import 'dart:io' show Platform;
import 'package:path/path.dart' as path;

ffi.DynamicLibrary loadNativeLibrary(String libraryName) {
  if (Platform.isMacOS || Platform.isIOS) {
    return ffi.DynamicLibrary.open('lib$libraryName.dylib');
  } else if (Platform.isWindows) {
    return ffi.DynamicLibrary.open('$libraryName.dll');
  } else {
    return ffi.DynamicLibrary.open('lib$libraryName.so');
  }
}

final dylib = loadNativeLibrary('hello');
实现平台特定的路由来加载动态库。
dart
import 'dart:ffi' as ffi;
import 'dart:io' show Platform;
import 'package:path/path.dart' as path;

ffi.DynamicLibrary loadNativeLibrary(String libraryName) {
  if (Platform.isMacOS || Platform.isIOS) {
    return ffi.DynamicLibrary.open('lib$libraryName.dylib');
  } else if (Platform.isWindows) {
    return ffi.DynamicLibrary.open('$libraryName.dll');
  } else {
    return ffi.DynamicLibrary.open('lib$libraryName.so');
  }
}

final dylib = loadNativeLibrary('hello');

4. Manually Map Types and Look Up Functions (If not using
ffigen
)

4. 手动映射类型与查找函数(若不使用
ffigen

Define the C signature and the Dart signature. Prefer
AbiSpecificInteger
types for standard C types.
dart
import 'dart:ffi' as ffi;

// C signature: void hello_world();
typedef hello_world_func = ffi.Void Function();
// Dart signature
typedef HelloWorld = void Function();

// C signature: long process_data(size_t size);
// PREFER AbiSpecificInteger (Long, Size) over Int64/Int32
typedef process_data_func = ffi.Long Function(ffi.Size size);
typedef ProcessData = int Function(int size);

final HelloWorld hello = dylib
    .lookup<ffi.NativeFunction<hello_world_func>>('hello_world')
    .asFunction();
定义C签名和Dart签名。对于标准C类型,优先使用
AbiSpecificInteger
类型。
dart
import 'dart:ffi' as ffi;

// C signature: void hello_world();
typedef hello_world_func = ffi.Void Function();
// Dart signature
typedef HelloWorld = void Function();

// C signature: long process_data(size_t size);
// PREFER AbiSpecificInteger (Long, Size) over Int64/Int32
typedef process_data_func = ffi.Long Function(ffi.Size size);
typedef ProcessData = int Function(int size);

final HelloWorld hello = dylib
    .lookup<ffi.NativeFunction<hello_world_func>>('hello_world')
    .asFunction();

5. Manage Native Memory and Resources

5. 管理原生内存与资源

When allocating memory in Dart to pass to C, use
calloc
. When wrapping C-allocated memory, use
Finalizable
and
NativeFinalizer
to prevent memory leaks.
dart
import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart'; // Provides calloc, malloc, Utf8

// 1. Manual Allocation
ffi.Pointer<Utf8> allocateString(String dartString) {
  // DO manually allocate memory using calloc
  final ffi.Pointer<Utf8> cString = dartString.toNativeUtf8(allocator: calloc);
  return cString;
}

void freeMemory(ffi.Pointer pointer) {
  // DO manually free memory
  calloc.free(pointer);
}

// 2. Native Finalizer for C-allocated memory
// Assume C provides: void free_resource(Resource* res);
final ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer)>> freeResourcePtr = 
    dylib.lookup('free_resource');

final ffi.NativeFinalizer _finalizer = ffi.NativeFinalizer(freeResourcePtr.cast());

class NativeResourceWrapper implements ffi.Finalizable {
  final ffi.Pointer<ffi.Void> _cResource;

  NativeResourceWrapper(this._cResource) {
    // DO use Finalizable and NativeFinalizer to ensure cleanup
    _finalizer.attach(this, _cResource.cast(), detach: this);
  }

  void dispose() {
    _finalizer.detach(this);
    // Manually call the free function if disposed early
    final freeFunc = freeResourcePtr.asFunction<void Function(ffi.Pointer)>();
    freeFunc(_cResource);
  }
}
当在Dart中分配内存以传递给C时,使用
calloc
。当包装C分配的内存时,使用
Finalizable
NativeFinalizer
来防止内存泄漏。
dart
import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart'; // Provides calloc, malloc, Utf8

// 1. Manual Allocation
ffi.Pointer<Utf8> allocateString(String dartString) {
  // DO manually allocate memory using calloc
  final ffi.Pointer<Utf8> cString = dartString.toNativeUtf8(allocator: calloc);
  return cString;
}

void freeMemory(ffi.Pointer pointer) {
  // DO manually free memory
  calloc.free(pointer);
}

// 2. Native Finalizer for C-allocated memory
// Assume C provides: void free_resource(Resource* res);
final ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer)>> freeResourcePtr = 
    dylib.lookup('free_resource');

final ffi.NativeFinalizer _finalizer = ffi.NativeFinalizer(freeResourcePtr.cast());

class NativeResourceWrapper implements ffi.Finalizable {
  final ffi.Pointer<ffi.Void> _cResource;

  NativeResourceWrapper(this._cResource) {
    // DO use Finalizable and NativeFinalizer to ensure cleanup
    _finalizer.attach(this, _cResource.cast(), detach: this);
  }

  void dispose() {
    _finalizer.detach(this);
    // Manually call the free function if disposed early
    final freeFunc = freeResourcePtr.asFunction<void Function(ffi.Pointer)>();
    freeFunc(_cResource);
  }
}

6. Validate and Fix

6. 验证与修复

After implementing the bindings, instruct the user to run a basic test script.
  • Validation: Does the Dart code successfully invoke the C function without a segmentation fault?
  • Fix: If an
    Invalid argument(s): Unknown library
    error occurs, verify the library path and ensure the library is compiled for the correct architecture (e.g., ARM64 vs x86_64). On macOS, ensure the library is signed if running in a strict environment.
实现绑定后,指导用户运行基础测试脚本。
  • 验证: Dart代码能否成功调用C函数且不出现段错误?
  • 修复: 若出现
    Invalid argument(s): Unknown library
    错误,请验证库路径,并确保库是针对正确架构编译的(例如ARM64 vs x86_64)。在macOS上,若运行在严格环境中,需确保库已签名。

Constraints

约束条件

  • DO NOT use fixed-width integers (e.g.,
    Int32
    ,
    Int64
    ) for C
    long
    ,
    int
    , or
    size_t
    . You MUST use
    abiSpecificInteger
    types (
    ffi.Long
    ,
    ffi.Int
    ,
    ffi.Size
    ).
  • DO NOT leave native memory unmanaged. Any pointer allocated via
    calloc
    or
    malloc
    MUST have a corresponding
    free
    call or be managed by an allocator lifecycle.
  • DO NOT manually write bindings for C headers exceeding 10 functions/structs. You MUST use
    package:ffigen
    .
  • DO NOT block the main Dart isolate with long-running C functions. For heavy C computation, you MUST integrate with
    dart-concurrency-isolates
    to run the FFI calls on a background worker.
  • DO NOT assume dynamic library paths are absolute. Always construct paths dynamically using
    dart:io
    Platform
    checks and
    package:path
    .
  • 请勿对C的
    long
    int
    size_t
    使用固定宽度整数(如
    Int32
    Int64
    )。必须使用
    abiSpecificInteger
    类型(
    ffi.Long
    ffi.Int
    ffi.Size
    )。
  • 请勿让原生内存处于未管理状态。任何通过
    calloc
    malloc
    分配的指针必须有对应的
    free
    调用,或由分配器生命周期管理。
  • 请勿为超过10个函数/结构体的C头文件手动编写绑定。必须使用
    package:ffigen
  • 请勿使用长时间运行的C函数阻塞Dart主隔离区。对于重型C计算,必须与
    dart-concurrency-isolates
    集成,在后台工作线程上运行FFI调用。
  • 请勿假设动态库路径是绝对路径。始终使用
    dart:io
    Platform
    检查和
    package:path
    动态构造路径。