使用 dart:ffi 进行 C 语言互操作
运行在 Dart 原生平台上的 Dart 移动、命令行和服务器应用可以使用 dart:ffi
库调用原生 C API,以及读、写、分配和释放原生内存。FFI 是 Foreign Function Interface 的缩写,意为外部函数接口。类似功能的其他术语包括原生接口和语言绑定。
API 文档可在dart:ffi
API 参考中查阅。
下载示例文件
#要使用本指南中的示例,请下载完整的 ffi 示例目录。它包括以下示例,展示如何使用 dart:ffi
库
示例 | 描述 |
---|---|
hello_world | 如何调用没有参数和返回值的 C 函数。 |
primitives | 如何调用具有整型或指针参数和返回值的 C 函数。 |
structs | 如何使用结构体在 C 语言中传递字符串以及处理简单和复杂的 C 结构体。 |
test_utils | 这些示例通用的测试工具。 |
回顾 hello_world 示例
#要了解如何使用 dart:ffi
库调用 C 函数,请回顾 hello.dart
文件。本节解释了该文件的内容。
文件
#hello_world
示例包含以下文件
源文件 | 描述 |
---|---|
hello.dart | 一个使用 C 库中 hello_world() 函数的 Dart 文件。 |
pubspec.yaml | Dart pubspec 文件,SDK 下限为 3.4。 |
hello_library/hello.h | 声明 hello_world() 函数。 |
hello_library/hello.c | 一个导入 hello.h 并定义 hello_world() 函数的 C 文件。 |
hello_library/hello.def | 一个模块定义文件,用于指定构建 DLL 时使用的信息。 |
hello_library/CMakeLists.txt | 一个用于将 C 代码编译成动态库的 CMake 构建文件。 |
构建 C 库会创建几个文件,包括名为 libhello.dylib
(macOS)、libhello.dll
(Windows) 或 libhello.so
(Linux) 的动态库文件。
构建和执行
#构建动态库并执行 Dart 应用的命令序列类似于以下内容。
cd hello_library
cmake .
...
make
...
cd ..
dart pub get
dart run hello.dart
Hello World
利用 dart:ffi
#要了解如何使用 dart:ffi
库调用 C 函数,请回顾 hello.dart
文件。本节解释了该文件的内容。
导入
dart:ffi
。dartimport 'dart:ffi' as ffi;
导入 path 库,该库将用于存储动态库的路径。
dartimport 'dart:io' show Platform, Directory; import 'package:path/path.dart' as path;
使用 C 函数的 FFI 类型签名创建 typedef。
要了解dart:ffi
库中最常用的类型,请查阅与原生类型交互。darttypedef hello_world_func = ffi.Void Function();
为调用 C 函数时要使用的变量创建
typedef
。darttypedef HelloWorld = void Function();
创建一个变量来存储动态库的路径。
dartfinal 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', 'Debug', 'hello.dll', ); } else { libraryPath = path.join( Directory.current.path, 'hello_library', 'libhello.so', ); }
打开包含 C 函数的动态库。
dartfinal dylib = ffi.DynamicLibrary.open(libraryPath);
获取 C 函数的引用,并将其存入变量。此代码使用步骤 2 和 3 中的
typedefs
,以及步骤 4 中的动态库变量。dartfinal HelloWorld hello = dylib .lookup<ffi.NativeFunction<hello_world_func>>('hello_world') .asFunction();
调用 C 函数。
darthello();
理解 hello_world
示例后,请查阅其他 dart:ffi
示例。
打包并加载 C 库
#打包/封装/分发然后加载原生 C 库的方法取决于平台和库类型。
要了解具体方法,请查阅以下页面和示例。
- Flutter
dart:ffi
用于 Android 应用 - Flutter
dart:ffi
用于 iOS 应用 - Flutter
dart:ffi
用于 macOS 应用 dart:ffi
示例
与原生类型交互
#dart:ffi
库提供了多种实现 NativeType
并表示 C 语言原生类型的类型。您可以实例化某些原生类型。另一些原生类型只能用作类型签名中的标记。
可以实例化这些类型签名标记
#以下原生类型可用作类型签名中的标记。它们或其子类型可以在 Dart 代码中实例化。
Dart 类型 | 描述 |
---|---|
Array | 固定大小的条目数组。特定类型数组的超类型。 |
Pointer | 表示指向原生 C 内存的指针。 |
Struct | 所有 FFI 结构体类型的超类型。 |
Union | 所有 FFI 联合体类型的超类型。 |
仅作为类型签名标记
#以下列表显示了哪些平台无关的原生类型可用作类型签名中的标记。它们不能在 Dart 代码中实例化。
Dart 类型 | 描述 |
---|---|
Bool | 表示 C 语言中的原生 bool 类型。 |
Double | 表示 C 语言中的原生 64 位 double 类型。 |
Float | 表示 C 语言中的原生 32 位 float 类型。 |
Int8 | 表示 C 语言中的原生带符号 8 位整数。 |
Int16 | 表示 C 语言中的原生带符号 16 位整数。 |
Int32 | 表示 C 语言中的原生带符号 32 位整数。 |
Int64 | 表示 C 语言中的原生带符号 64 位整数。 |
NativeFunction | 表示 C 语言中的函数类型。 |
Opaque | C 语言中所有不透明类型的超类型。 |
Uint8 | 表示 C 语言中的原生无符号 8 位整数。 |
Uint16 | 表示 C 语言中的原生无符号 16 位整数。 |
Uint32 | 表示 C 语言中的原生无符号 32 位整数。 |
Uint64 | 表示 C 语言中的原生无符号 64 位整数。 |
Void | 表示 C 语言中的 void 类型。 |
还有许多特定于 ABI 的标记原生类型,它们扩展自 AbiSpecificInteger。要了解这些类型在特定平台上的映射方式,请查阅下表中链接的 API 文档。
Dart 类型 | 描述 |
---|---|
AbiSpecificInteger | 所有 ABI 特定整数类型的超类型。 |
Int | 表示 C 语言中的 int 类型。 |
IntPtr | 表示 C 语言中的 intptr_t 类型。 |
Long | 表示 C 语言中的 long int (即 long ) 类型。 |
LongLong | 表示 C 语言中的 long long 类型。 |
Short | 表示 C 语言中的 short 类型。 |
SignedChar | 表示 C 语言中的 signed char 类型。 |
Size | 表示 C 语言中的 size_t 类型。 |
UintPtr | 表示 C 语言中的 uintptr_t 类型。 |
UnsignedChar | 表示 C 语言中的 unsigned char 类型。 |
UnsignedInt | 表示 C 语言中的 unsigned int 类型。 |
UnsignedLong | 表示 C 语言中的 unsigned long int 类型。 |
UnsignedLongLong | 表示 C 语言中的 unsigned long long 类型。 |
UnsignedShort | 表示 C 语言中的 unsigned short 类型。 |
WChar | 表示 C 语言中的 wchar_t 类型。 |
使用 package:ffigen
生成 FFI 绑定
#对于大型 API 接口,手动编写与 C 代码集成的 Dart 绑定可能非常耗时。要让 Dart 从 C 头文件创建 FFI 包装器,请使用 package:ffigen
绑定生成器。
构建并打包原生代码
#Dart 构建钩子(以前称为原生资产)使包除了 Dart 源代码外,还能包含更多内容。现在,包可以包含原生代码资产,这些资产会被透明地构建、打包并在运行时可用。
此功能简化了 Dart 包依赖和使用原生代码的方式
- 使用包的构建钩子(位于
hook/build.dart
中)构建原生代码或获取二进制文件。 - Dart 和 Flutter 会打包构建钩子报告的
CodeAsset
。 - 通过使用
assetId
的声明式@Native<>() extern
函数在运行时访问代码资产。
Flutter 和独立的 Dart 会自动打包应用使用的所有包的原生代码,并在运行时使其可用。这适用于 flutter (run|build)
和 dart (run|build)
命令。
回顾 native_add_library
示例
#native_add_library
示例包含了在 Dart 包中构建和打包 C 代码所需的最低代码。
该示例包含以下文件
源文件 | 描述 |
---|---|
src/native_add_library.c | 包含 add 函数代码的 C 文件。 |
lib/native_add_library.dart | 通过 FFI 调用资产 package:native_add_library/native_add_library.dart 中 C 函数 add 的 Dart 文件。(注意,资产 ID 默认使用库 URI。) |
test/native_add_library_test.dart | 一个使用原生代码的 Dart 测试。 |
hook/build.dart | 一个构建钩子,用于编译 src/native_add_library.c 并声明 ID 为 package:native_add_library/native_add_library.dart 的已编译资产。 |
当 Dart 或 Flutter 项目依赖于 package:native_add_library
时,在运行 run
、build
和 test
命令时会调用 hook/build.dart
构建钩子。native_add_app
示例展示了 native_add_library
的用法。
回顾构建钩子 API 文档
#以下包的 API 文档可以在这里找到
- 要了解 Dart FFI 中对代码资产的支持,请查阅
dart:ffi
API 参考中的Native
和DefaultAsset
。 - 要了解
hook/build.dart
构建钩子,请查阅package:hooks
API 参考。
提供反馈
#要提供反馈,请参考这些跟踪议题