在 macOS 中使用 dart:ffi 调用本地代码
Flutter 移动版可以使用 dart:ffi 库来调用本地的 C API。 FFI 代表 外部功能接口。类似功能的其他术语包括 本地接口 和 语言绑定。
您必须首先确保本地代码已加载,并且其符号对 Dart 可见,然后才能在库或程序使用 FFI 库绑定本地代码。本页主要介绍如何在 Flutter 插件或应用程序中编译、打包和加载 macOS 本地代码。
本教程演示了如何在 Flutter 插件中捆绑 C/C++ 源代码,并使用 macOS 上的 Dart FFI 库绑定它们。在本示例中,您将创建一个实现 32 位的加法 C 函数,然后通过名为 “native_add” 的 Dart 插件暴露它。
动态链接 vs 静态链接
本地库可以动态或静态地链接到应用程序中。一个静态链接库会被嵌入到应用程序的可执行映像中,并在应用程序启动时加载。
静态链接中的符号可以使用 DynamicLibrary.executable
或 DynamicLibrary.process
来加载。
相比之下,动态链接库则分布在应用程序中的单独的文件或文件夹中,并按需加载。在 macOS 上,它是作为 .framework
文件夹分发的。
动态链接库在 Dart 中可以通过 DynamicLibrary.open
加载。
Dart dev 频道中的 API 已经可用: Dart API 参考文档。
步骤 1:创建插件
如果您已经有一个插件,跳过这步。
如果要创建一个名为 “native_add” 的插件,您需要这么做:
$ flutter create --platforms=macos --template=plugin native_add
$ cd native_add
步骤 2:添加 C/C++ 源码
您需要让 macOS 构建系统知道本地代码的存在,以便代码可以被编译并链接到最终的应用程序中。
您可以将源代码添加到 macos
文件夹,因为 CocoaPods 不允许源码处于比 podspec
文件更高的目录层级,
FFI 库只能与 C 符号绑定,因此在 C++ 中,这些符号添加 extern C
标记。还应该添加属性来表明符号是需要被 Dart 引用的,以防止链接器在优化链接时会丢弃符号。
作为示例,创建一个 C++ 文件,路径为:macos/Classes/native_add.cpp
。(请注意,模板已经为您创建了此文件。)在项目的根目录下中执行以下命令:
cat > macos/Classes/native_add.cpp << EOF
#include <stdint.h>
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) {
return x + y;
}
EOF
在 macOS 中,您需要告诉 Xcode 如何静态链接这个文件:
-
在 Xcode 中,打开
Runner.xcworkspace
。 -
添加 C/C++/Objective-C/Swift 源码文件到 Xcode 工程中。
Step 3: Load the code using the FFI library
在示例中,您需要添加如下的代码到 lib/native_add.dart
。但是,Dart 在何处进行代码绑定并不重要。
首先,您需要创建一个 DynamicLibrary
来处理本地代码。
import 'dart:ffi'; // For FFI
final DynamicLibrary nativeAddLib = DynamicLibrary.process();
您可以通过使用库的句柄来解析 native_add
符号:
final int Function(int x, int y) nativeAdd = nativeAddLib
.lookup<NativeFunction<Int32 Function(Int32, Int32)>>('native_add')
.asFunction();
现在,您可以调用它了。在自动生成的 example 项目(example/lib/main.dart
)中演示它。
// Inside of _MyAppState.build:
body: Center(
child: Text('1 + 2 == ${nativeAdd(1, 2)}'),
),
其他的用例
iOS 和 macOS
动态链接库在应用程序启动时由动态链接器自动加载。它们的组成符号可以用 DynamicLibrary.process
。您还可以使用 DynamicLibrary.open
来限制符号解析的范围,但目前仍然不确定苹果的审查程序将如何处理两者的使用。
您可以使用 DynamicLibrary.executable
或 DynamicLibrary.process
解析静态链接到应用程序二进制文件的符号。
平台库
要链接到平台库,请按照如下说明:
-
在 Xcode 中,打开
Runner.xcworkspace
。 -
选择目标设备。
-
在 Linked Frameworks and Libraries 中点击 +。
-
选择要链接的系统库。
第一方库
第一方本地库可以作为源文件或(已签名的).framework
文件被包含在内。它也可能包括静态链接的档案,但需要测试。
源码
要直接链接到源代码,请按照如下说明:
-
在 Xcode 中,打开
Runner.xcworkspace
。 -
添加 C/C++/Objective-C/Swift 源码到 Xcode 工程中。
-
将以下前缀添加到导出的符号声明中,以确保它们对 Dart 可见:
C/C++/Objective-C
extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
Swift
@_cdecl("myFunctionName")
已编译的动态库
要链接到已编译过的动态库,请按照如下说明:
-
如果存在已进行签名的
Framework
文件,请打开Runner.xcworkspace
。 -
添加 framework 文件到 Embedded Binaries 区域中。
-
同时将其添加到 Xcode 中目标的 Linked Frameworks & Libraries 部分。
已编译的(动态)库 (macOS)
要添加一个闭源的库到 Flutter macOS 桌面 应用,请按照如下说明:
-
按照 Flutter 桌面的使用说明来创建 Flutter 桌面应用程序。
-
在 Xcode 中打开
yourapp/macos/Runner.xcworkspace
。-
拖动您已经预编译的
libyourlibrary.dylib
到您的Runner/Frameworks
。 -
点击
Runner
然后进入Build Phases
标签。-
拖动
libyourlibrary.dylib
到Copy Bundle Resources
列表。 -
在
Embed Libararies
下,检查Code Sign on Copy
。 -
在
Link Binary With Libraries
下,设置状态为Optional
。(我们使用动态链接,不需要静态链接)
-
-
点击
Runner
然后进入General
标签页。-
拖动
libyourlibrary.dylib
到 Frameworks, Libararies and Embedded Content 列表中。 -
选择 Embed & Sign。
-
-
点击
Runner
然后进入Build Settings
标签页。-
在
Search Paths
部分,配置Library Search Paths
确保libyourlibrary.dylib
的路径包括在内。
-
-
-
编辑
lib/main.dart
文件。-
使用
DynamicLibrary.open('libyourlibrary.dylib')
来动态链接符号表。 -
在 widget 的某个地方调用您的本地代码。
-
-
运行
flutter run
然后检查您的本地方法的调用结果。 -
运行
flutter build macos
去构建一个自包含的 release 版本的应用。
Other Resources
To learn more about C interoperability, check out these videos: