Dart FFI 完全指南:从入门到实战

Dart FFI 完全指南:从入门到实战

Dart FFI(Foreign Function Interface)是 Dart 官方提供的高性能互操作机制,允许 Dart 代码直接调用 C/C++ 编写的原生代码。对于 Flutter 开发者来说,当遇到计算密集型任务(如图像处理、复杂算法)或需要复用现有的 C/C++ 代码库时,FFI 是不可或缺的工具。

本文将详细讲解 Dart FFI 的使用方法,从最简单的示例到实际项目集成,一步步带你掌握这门技术。


一、FFI 的基本概念

在开始写代码之前,先理解几个核心概念:

Dart FFI 是什么?

  • 它是 Dart 与 C 语言之间的“桥梁”,让 Dart 代码能调用 C 语言的函数,读写 C 语言的内存。
  • 注意:FFI 只能直接绑定 C 符号,所以 C++ 函数必须用 extern "C" 包裹。

两种集成方式:源码集成 vs 预编译库

方式说明适用场景
源码集成.c/.cpp 源码放在项目中,由构建系统编译并链接进 App第一方代码,需要方便调试和修改
预编译库DynamicLibrary.open() 加载 .so/.framework 等动态库使用闭源的第三方库

动态链接 vs 静态链接

  • 动态链接库:以独立文件分发,按需加载,用 DynamicLibrary.open() 打开
  • 静态链接库:嵌入 App 可执行文件,应用启动时加载,用 DynamicLibrary.executableDynamicLibrary.process 解析符号

二、从零开始:Hello World 示例

这是最小可行的 FFI 示例,包含 C 代码和 Dart 调用代码。

2.1 C 代码(hello_library/hello.c)

#include <stdio.h>

void hello_world() {
    printf("Hello World from C!\n");
}

头文件(hello_library/hello.h):

#ifndef HELLO_H
#define HELLO_H

void hello_world();

#endif

2.2 Dart 代码

第一步:导入库并设置路径

import 'dart:ffi' as ffi;
import 'dart:io' show Platform, Directory;
import 'package:path/path.dart' as path;

final String libraryPath;
if (Platform.isMacOS) {
  libraryPath = path.join(
    Directory.current.path,
    'hello_library',
    'libhello.dylib',
  );
} else if (Platform.isWindows) {
  libraryPath = path.join(
    Directory.current.path,
    'hello_library',
    'hello.dll',
  );
} else {
  // Linux / Android
  libraryPath = path.join(
    Directory.current.path,
    'hello_library',
    'libhello.so',
  );
}

第二步:定义类型签名并加载函数

// 步骤1:用 FFI 类型签名定义 C 函数原型
typedef hello_world_func = ffi.Void Function();

// 步骤2:为 Dart 调用定义对应的 Dart 函数类型
typedef HelloWorld = void Function();

// 步骤3:打开动态库
final dylib = ffi.DynamicLibrary.open(libraryPath);

// 步骤4:查找 C 函数并转换为 Dart 可调用函数
final HelloWorld hello = dylib
    .lookup<ffi.NativeFunction<hello_world_func>>('hello_world')
    .asFunction();

// 步骤5:调用!
hello();

2.3 编译和运行

在 hello_library 目录下使用 CMake 编译:

cd hello_library
cmake .
make  # 或 cmake --build .
cd ..
dart pub get
dart run hello.dart
# 输出:Hello World from C!

三、更复杂的示例:传递参数和返回值

3.1 C 代码:带参数的加法函数

// src/native_add.c
#include <stdint.h>

// 关键:extern "C" 防止 C++ 名称修饰
// __attribute__ 防止链接器优化时丢弃符号
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t add(int32_t a, int32_t b) {
    return a + b;
}

3.2 Dart 绑定

import 'dart:ffi' as ffi;

// C 函数签名:int32_t add(int32_t a, int32_t b)
typedef AddFunc = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);
typedef Add = int Function(int a, int b);

final dylib = ffi.DynamicLibrary.open('libnative_add.so');
final Add add = dylib
    .lookup<ffi.NativeFunction<AddFunc>>('add')
    .asFunction();

void main() {
  print('1 + 2 = ${add(1, 2)}');  // 输出:1 + 2 = 3
}

3.3 常用 FFI 类型映射

C 类型Dart FFI 类型Dart 普通类型
voidffi.Voidvoid
intffi.Int32 / ffi.Int64int
floatffi.Floatdouble
doubleffi.Doubledouble
char*ffi.Pointer<ffi.Char>Pointer<Char>
uint8_t*ffi.Pointer<ffi.Uint8>Pointer<Uint8>

四、字符串和内存操作

4.1 从 C 返回字符串

C 代码

#include <stdlib.h>
#include <string.h>

extern "C" __attribute__((visibility("default")))
char* get_message() {
    char* msg = (char*)malloc(20);
    strcpy(msg, "Hello from C!");
    return msg;
}

extern "C" __attribute__((visibility("default")))
void free_message(char* msg) {
    free(msg);
}

Dart 代码

import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';  // 提供 toDartString() 等工具

typedef GetMessageFunc = ffi.Pointer<ffi.Char> Function();
typedef GetMessage = ffi.Pointer<ffi.Char> Function();

typedef FreeMessageFunc = ffi.Void Function(ffi.Pointer<ffi.Char>);
typedef FreeMessage = void Function(ffi.Pointer<ffi.Char>);

final getMessage = dylib
    .lookup<ffi.NativeFunction<GetMessageFunc>>('get_message')
    .asFunction();
final freeMessage = dylib
    .lookup<ffi.NativeFunction<FreeMessageFunc>>('free_message')
    .asFunction();

void main() {
  final ptr = getMessage();
  print(ptr.cast<Utf8>().toDartString());  // 输出:Hello from C!
  freeMessage(ptr);  // 重要:释放 C 分配的内存!
}

4.2 Dart 传递字符串给 C

import 'package:ffi/ffi.dart';

// C 函数:void process_string(const char* str)
typedef ProcessStringFunc = ffi.Void Function(ffi.Pointer<ffi.Char>);
typedef ProcessString = void Function(ffi.Pointer<ffi.Char>);

void main() {
  final ProcessString process = dylib
      .lookup<ffi.NativeFunction<ProcessStringFunc>>('process_string')
      .asFunction();
  
  // 使用 toNativeUtf8() 将 Dart 字符串转为 C 字符串
  final str = 'Hello Dart!'.toNativeUtf8();
  process(str);
  calloc.free(str);  // 释放分配的内存
}

五、在 Flutter 项目中集成 C++ 源码

官方推荐使用 package_ffi 模板来创建 FFI 包,它通过 build.dart 构建钩子自动编译原生代码,无需手动配置各平台的构建文件。

5.1 创建 FFI 包

flutter create --template=package_ffi native_add
cd native_add

生成的目录结构:

native_add/
├── lib/
│   ├── native_add.dart           # 公开 API
│   └── native_add_bindings_generated.dart  # ffigen 生成的绑定
├── src/
│   ├── native_add.c              # C 源码
│   └── native_add.h              # C 头文件
├── hook/
│   └── build.dart                # 构建钩子(自动编译原生代码)
├── ffigen.yaml                   # ffigen 配置
└── pubspec.yaml

5.2 C++ 源码的写法

// src/native_add.cpp
#include <stdint.h>

// 必须用 extern "C"
// 必须添加可见性属性,防止符号被丢弃
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t add(int32_t a, int32_t b) {
    return a + b;
}

extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t multiply(int32_t a, int32_t b) {
    return a * b;
}

5.3 使用 ffigen 自动生成绑定

ffigen.yaml 配置:

name: NativeAddBindings
description: Bindings for native_add
output: lib/native_add_bindings_generated.dart
headers:
  entry-points:
    - src/native_add.h
  include-directives:
    - src/native_add.h
compiler-opts:
  - -I.
  - -Isrc

生成绑定:

dart run ffigen

生成的代码会包含类型安全的 Dart 函数,可以直接调用。

5.4 在 Flutter 应用中引用

// lib/native_add.dart
import 'native_add_bindings_generated.dart';

class NativeAdd {
  static final NativeAddBindings _bindings = NativeAddBindings();

  static int add(int a, int b) => _bindings.add(a, b);
  static int multiply(int a, int b) => _bindings.multiply(a, b);
}

六、旧版 plugin_ffi 模板(如需使用 Flutter Plugin API)

如果你需要访问 Flutter Plugin API(如在 Swift/Objective-C 中调用平台功能),可以使用旧版 plugin_ffi 模板:

flutter create --platforms=android,ios,macos,windows,linux --template=plugin_ffi native_add

这个模板会生成各平台的构建文件:

  • Android:native_add/android/build.gradle(调用 CMake)
  • iOS/macOS:native_add/ios/native_add.podspec
  • Linux:native_add/linux/CMakeLists.txt
  • Windows:native_add/windows/CMakeLists.txt

七、常见问题与注意事项

7.1 为什么 C++ 必须用 extern “C”?

C++ 编译器会对函数名进行“名称修饰”(name mangling),改变符号名。extern "C" 告诉编译器使用 C 语言的命名规则,这样 Dart 才能通过函数名字符串找到它。

7.2 符号找不到怎么办?

添加 __attribute__((visibility("default"))) __attribute__((used)) 确保符号被导出且不会被链接器优化掉。

7.3 Android 只支持动态链接

因为 Android 的主可执行文件是 JVM,Flutter 无法静态链接到它,所以必须使用动态库(.so 文件)。

7.4 iOS 加载动态库的注意事项

iOS 上动态库以 .framework 形式分发,需要正确签名并嵌入 Xcode 项目。

7.5 性能优化建议

对于执行时间较长的算法,建议在 辅助 Isolate 中执行 FFI 调用,避免阻塞 UI 主线程。


八、总结

Dart FFI 的核心流程可以用这张图概括:

Dart 代码 → 定义类型签名 → 加载动态库 → 查找符号 → 转换为 Dart 函数 → 调用

最佳实践:

  1. 使用 package_ffi 模板创建新的 FFI 包(Flutter 3.38+ 推荐)
  2. 利用 ffigen 自动生成绑定,减少手动工作量
  3. C++ 函数必须用 extern "C" 导出,并添加可见性属性
  4. 注意内存管理:C 分配的内存需要由 C 的释放函数释放
  5. 耗时操作放到 Isolate 中执行

掌握了这些知识,你就可以在 Flutter 项目中自由调用 C/C++ 算法库,享受原生性能带来的优势了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐诺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值