Dart FFI (Foreign Function Interface) 允许 Dart 代码直接调用用 C 语言编写的库(或任何其他能够导出 C 风格 API 的语言,如 C++, Rust, Go 等)。这对于需要高性能计算、与现有原生库集成或访问操作系统底层功能的场景非常有用。Flutter 应用可以通过 FFI 调用原生平台的 C/C++ 库,从而扩展其能力。
1. 为什么使用 Dart FFI?
2. 核心概念
dart:ffi 库:Dart 提供的 FFI 库,包含了与 C 语言交互所需的所有类和函数。DynamicLibrary:用于加载动态链接库(如 .so on Linux, .dylib on macOS, .dll on Windows)。lookupFunction:用于查找 C 函数的指针,并将其转换为 Dart 函数。Pointer<T>:表示 C 语言中的指针。T 可以是 Int8、Int16、Int32、Int64、Float、Double、Void 等 FFI 类型。NativeFunction:表示 C 语言中的函数类型。asFunction<F extends Function>():将 Pointer<NativeFunction<F>> 转换为 Dart 函数 F。Struct 和 Union:用于表示 C 语言中的结构体和联合体。Allocator:用于在 C 堆上分配和释放内存。3. 基本步骤
.so, .dylib, .dll)。DynamicLibrary.open() 加载编译好的动态链接库。lookupFunction 将 C 函数转换为 Dart 函数,然后像调用普通 Dart 函数一样调用它。示例:Dart 调用 C 语言的加法函数
步骤 1: 编写 C 语言代码 (c_add.c)
// c_add.c
#include <stdio.h>
// 一个简单的加法函数
int add(int a, int b) {
printf("C: Adding %d and %d\n", a, b);
return a + b;
}
// 一个返回字符串的函数 (需要注意内存管理)
const char* greet(const char* name) {
static char buffer[256]; // 静态缓冲区,避免返回栈内存
snprintf(buffer, sizeof(buffer), "Hello, %s from C!", name);
return buffer;
}
步骤 2: 编译 C 语言代码
gcc -shared -o libc_add.so c_add.c # Linux
clang -shared -o libc_add.dylib c_add.c # macOS
gcc -shared -o c_add.dll c_add.c
将生成的动态链接库文件(例如 libc_add.so)放置在 Flutter 项目的某个位置,例如 lib/ 目录下,或者在运行时可访问的路径。
步骤 3: 在 Dart 中加载并调用
import 'dart:ffi'; // 导入 FFI 库
import 'dart:io'; // 用于判断平台
import 'package:ffi/ffi.dart'; // 用于内存分配和字符串转换
// 定义 C 函数的类型签名
typedef C_Add = Int32 Function(Int32 a, Int32 b);
typedef Dart_Add = int Function(int a, int b);
typedef C_Greet = Pointer<Utf8> Function(Pointer<Utf8> name);
typedef Dart_Greet = Pointer<Utf8> Function(Pointer<Utf8> name);
// 获取动态链接库的路径
DynamicLibrary _openDynamicLibrary() {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('libc_add.dylib');
} else if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('libc_add.so');
} else if (Platform.isWindows) {
return DynamicLibrary.open('c_add.dll');
} else {
throw UnsupportedError('Unknown platform');
}
}
void main() {
// 加载动态链接库
final DynamicLibrary nativeLib = _openDynamicLibrary();
// 查找并转换为 Dart 函数 (add)
final Dart_Add add = nativeLib
.lookupFunction<C_Add, Dart_Add>('add');
// 调用 C 函数
int result = add(10, 20);
print('Dart: Result from C add function: $result');
// 查找并转换为 Dart 函数 (greet)
final Dart_Greet greetNative = nativeLib
.lookupFunction<C_Greet, Dart_Greet>('greet');
// 准备 C 字符串
final Pointer<Utf8> namePtr = 'World'.toNativeUtf8();
// 调用 C 函数并获取返回的 C 字符串指针
final Pointer<Utf8> greetingPtr = greetNative(namePtr);
// 将 C 字符串转换为 Dart 字符串
final String greeting = greetingPtr.toDartString();
print('Dart: Result from C greet function: $greeting');
// 释放 C 字符串内存 (如果是由 Dart 分配的)
// calloc.free(namePtr); // 如果是 toNativeUtf8() 分配的,需要释放
}
Dart FFI 在 Flutter 开发中主要用于以下场景:
案例:使用 Dart FFI 进行图像处理 (灰度化)
我们将演示如何使用 Dart FFI 调用一个简单的 C 函数来对图像数据进行灰度化处理。这需要一个 C 库来执行实际的像素操作。
步骤 1: 编写 C 语言代码 (image_processor.c)
// image_processor.c
#include <stdint.h>
// 灰度化函数:将 RGBA 图像数据转换为灰度图像
// data: RGBA 像素数据数组
// width: 图像宽度
// height: 图像高度
void grayscale_image(uint8_t* data, int width, int height) {
for (int i = 0; i < width * height; ++i) {
uint8_t r = data[i * 4];
uint8_t g = data[i * 4 + 1];
uint8_t b = data[i * 4 + 2];
// 计算灰度值 (简单的平均值法)
uint8_t gray = (r + g + b) / 3;
data[i * 4] = gray; // R
data[i * 4 + 1] = gray; // G
data[i * 4 + 2] = gray; // B
// data[i * 4 + 3] 是 Alpha 通道,保持不变
}
}
步骤 2: 编译 C 语言代码
gcc -shared -o libimage_processor.so image_processor.c # Linux
clang -shared -o libimage_processor.dylib image_processor.c # macOS
gcc -shared -o image_processor.dll image_processor.c
将生成的动态链接库文件放置在 Flutter 项目的 lib/ 目录下。
步骤 3: 在 Flutter 中使用 FFI 进行图像处理
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // 用于加载图片资源
// 定义 C 函数的类型签名
typedef C_GrayscaleImage = Void Function(Pointer<Uint8> data, Int32 width, Int32 height);
typedef Dart_GrayscaleImage = void Function(Pointer<Uint8> data, int width, int height);
// 获取动态链接库的路径
DynamicLibrary _openDynamicLibrary() {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('libimage_processor.dylib');
} else if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('libimage_processor.so');
} else if (Platform.isWindows) {
return DynamicLibrary.open('image_processor.dll');
} else {
throw UnsupportedError('Unknown platform');
}
}
class ImageProcessingScreen extends StatefulWidget {
const ImageProcessingScreen({super.key});
@override
State<ImageProcessingScreen> createState() => _ImageProcessingScreenState();
}
class _ImageProcessingScreenState extends State<ImageProcessingScreen> {
late DynamicLibrary _nativeLib;
late Dart_GrayscaleImage _grayscaleImage;
Image? _originalImage;
Image? _processedImage;
@override
void initState() {
super.initState();
_nativeLib = _openDynamicLibrary();
_grayscaleImage = _nativeLib.lookupFunction<C_GrayscaleImage, Dart_GrayscaleImage>(
'grayscale_image',
);
_loadImage();
}
Future<void> _loadImage() async {
// 从 assets 加载图片
final ByteData data = await rootBundle.load('assets/images/sample_image.png'); // 确保你有这个图片文件
final Uint8List bytes = data.buffer.asUint8List();
// 将图片解码为 Image 对象
final Image image = Image.memory(bytes);
setState(() {
_originalImage = image;
});
}
Future<void> _processImage() async {
if (_originalImage == null) return;
// 获取图片的像素数据
final ByteData? byteData = await _originalImage!.image
.toByteData(format: ImageByteFormat.rawRgba);
if (byteData == null) return;
final Uint8List rgbaBytes = byteData.buffer.asUint8List();
// 在 C 堆上分配内存,并将 Dart 字节数据复制过去
final Pointer<Uint8> nativeData = calloc<Uint8>(rgbaBytes.length);
nativeData.asTypedList(rgbaBytes.length).setAll(0, rgbaBytes);
// 获取图片尺寸
final int width = _originalImage!.image.width;
final int height = _originalImage!.image.height;
// 调用 C 函数进行灰度化处理
_grayscaleImage(nativeData, width, height);
// 将处理后的数据复制回 Dart 的 Uint8List
final Uint8List processedRgbaBytes = nativeData.asTypedList(rgbaBytes.length);
// 释放 C 堆上的内存
calloc.free(nativeData);
// 将处理后的像素数据转换为 Image 对象
final Image processedImage = Image.memory(
processedRgbaBytes,
width: width.toDouble(),
height: height.toDouble(),
fit: BoxFit.cover,
);
setState(() {
_processedImage = processedImage;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FFI 图像处理示例'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (_originalImage != null)
Column(
children: [
const Text('原始图片:'),
SizedBox(
width: 200,
height: 200,
child: _originalImage,
),
],
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _processImage,
child: const Text('灰度化图片 (FFI)'),
),
const SizedBox(height: 20),
if (_processedImage != null)
Column(
children: [
const Text('处理后的图片:'),
SizedBox(
width: 200,
height: 200,
child: _processedImage,
),
],
),
],
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: ImageProcessingScreen()));
}
案例分析:
image_processor.c:包含一个简单的 grayscale_image C 函数,它接收图像的 RGBA 像素数据、宽度和高度,并直接修改像素数据以实现灰度化。lib/ 目录下。_openDynamicLibrary():一个辅助函数,根据当前平台加载正确的动态链接库。_grayscaleImage 函数指针:使用 _nativeLib.lookupFunction 将 C 语言的 grayscale_image 函数转换为 Dart 可以调用的函数。rootBundle.load('assets/images/sample_image.png'):从 Flutter 的 assets 中加载图片数据。_originalImage!.image.toByteData(format: ImageByteFormat.rawRgba):将 Image 对象转换为原始的 RGBA 字节数据。这是将图像数据传递给 C 函数的关键一步。calloc<Uint8>(rgbaBytes.length):在 C 堆上分配一块内存,大小与图像像素数据相同。ffi 包的 calloc 提供了方便的内存管理。nativeData.asTypedList(rgbaBytes.length).setAll(0, rgbaBytes):将 Dart 的 Uint8List 像素数据复制到 C 堆上分配的内存中。_grayscaleImage(nativeData, width, height):调用 C 函数,将 C 堆上的像素数据指针传递给它。nativeData.asTypedList(rgbaBytes.length):C 函数处理完成后,直接从 C 堆上的内存中读取处理后的像素数据,并将其转换为 Dart 的 Uint8List。calloc.free(nativeData):释放 C 堆上分配的内存,避免内存泄漏。Image.memory():使用处理后的像素数据创建新的 Image Widget 来显示灰度化后的图片。这个案例展示了 Dart FFI 在 Flutter 中进行高性能图像处理的潜力。通过 FFI,你可以将计算密集型任务卸载到 C/C++ 库中执行,从而显著提高应用程序的性能,同时保持 Flutter 的跨平台优势。
注意:在实际项目中,管理 C 语言库的编译和分发可能会比较复杂,特别是对于多平台支持。通常会使用 flutter_rust_bridge 或 dart_bindgen 等工具来简化 FFI 的绑定生成过程。