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.executable或DynamicLibrary.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 普通类型 |
|---|---|---|
void | ffi.Void | void |
int | ffi.Int32 / ffi.Int64 | int |
float | ffi.Float | double |
double | ffi.Double | double |
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 函数 → 调用
最佳实践:
- 使用
package_ffi模板创建新的 FFI 包(Flutter 3.38+ 推荐) - 利用
ffigen自动生成绑定,减少手动工作量 - C++ 函数必须用
extern "C"导出,并添加可见性属性 - 注意内存管理:C 分配的内存需要由 C 的释放函数释放
- 耗时操作放到 Isolate 中执行
掌握了这些知识,你就可以在 Flutter 项目中自由调用 C/C++ 算法库,享受原生性能带来的优势了。

741

被折叠的 条评论
为什么被折叠?



