跳转至正文

使用旧版 FFI plugin 模板绑定到原生代码

在 Flutter plugin 或应用中,使用旧版的 plugin_ffi 模板和 dart:ffi 与原生 C 代码进行绑定。

Flutter 移动和桌面应用可以使用 dart:ffi 库调用原生 C API。 FFI 代表 foreign function interface(外部函数接口)。类似功能的其他术语包括 原生接口 (native interface) 和 语言绑定 (language bindings)。

在你的库或程序可以使用 FFI 库绑定到原生代码之前,你必须确保原生代码已加载并且其 Symbols 符号对 Dart 可见。本页面重点介绍在 Flutter plugin 或应用中编译、打包、和加载原生代码。

本教程演示了如何在 Flutter plugin 中捆绑 C/C++ 源码并使用 Dart FFI 库绑定它们。在本演练中,你将创建一个实现 32 位加法的 C 函数,然后通过名为 native_add 的 Dart plugin 公开它。

动态链接与静态链接

#

原生库可以动态或静态地链接到应用中。静态链接库嵌入到应用的执行映像中,并在应用启动时加载。

可以使用 DynamicLibrary.executable 或 DynamicLibrary.process 加载静态链接库中的 Symbols 符号。

相比之下,动态链接库以单独的文件或文件夹形式分发在应用内,并按需加载。分发格式取决于平台:

  • 在 Android 上,动态链接库以一组 .so (ELF) 文件形式分发,每个架构一个。只支持动态库,因为主可执行文件是 JVM,而 Flutter 不会静态链接到它。

  • 在 iOS 和 macOS 上,动态链接库以 .framework 文件夹形式分发。

可以使用 DynamicLibrary.open 将动态链接库加载到 Dart 中。

创建 FFI plugin

#

要创建名为 native_add 的 FFI plugin,请使用 flutter create 和 plugin_ffi 模板:

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

这会在 native_add/src 中创建一个包含 C/C++ 源码的 plugin。这些源码由各个操作系统构建文件夹中的原生构建文件构建。

FFI 库只能绑定 C Symbols 符号,因此在 C++ 中,这些 Symbols 符号被标记为 extern "C"。

你还应该添加属性来指示这些 Symbols 符号是从 Dart 引用的,以防止链接器在链接时优化期间丢弃这些 Symbols 符号: __attribute__((visibility("default"))) __attribute__((used))。

平台特定的构建文件链接代码:

  • 在 Android 上,native_add/android/build.gradle。

  • 在 iOS 上,native_add/ios/native_add.podspec。

  • 在 macOS 上,native_add/macos/native_add.podspec。

  • 在 Linux 上,native_add/linux/CMakeLists.txt。

  • 在 Windows 上,native_add/windows/CMakeLists.txt。

原生代码从 Dart 中的 lib/native_add_bindings_generated.dart 调用。

绑定是使用 package:ffigen 生成的。

其他用例

#

iOS

#

动态链接器在应用启动时自动加载动态链接库。它们的组成 Symbols 符号可以使用 DynamicLibrary.process 解析。你还可以使用 DynamicLibrary.open 获取库的 handle 以限制 Symbols 符号解析的范围,但 Apple 的审核流程如何处理这一点尚不清楚。

静态链接到应用二进制文件中的 Symbols 符号可以使用 DynamicLibrary.executable 或 DynamicLibrary.process 解析。

平台库

#

要链接到平台库,请使用以下说明:

  1. 在 Xcode 中,打开 Runner.xcworkspace。

  2. 选择目标平台。

  3. 在 Linked Frameworks and Libraries 部分中点击 +。

  4. 选择要链接的系统库。

第一方库

#

第一方原生库可以作为源码或作为(签名).framework 文件包含。可能也可以包含静态链接的归档文件,但这需要测试。

源码

#

要直接链接到源码,请使用以下说明:

  1. 在 Xcode 中,打开 Runner.xcworkspace。

  2. 将 C/C++/Objective-C/Swift 源码文件添加到 Xcode 项目中。

  3. 将以下前缀添加到导出的 Symbol 符号声明中,以确保它们对 Dart 可见:

    C/C++/Objective-C:

    objc
    extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
    

    Swift:

    swift
    @_cdecl("myFunctionName")
    

已编译(动态)库

#

要链接到已编译的动态库,请使用以下说明:

  1. 如果存在正确签名的 Framework 文件,请打开 Runner.xcworkspace。

  2. 将 framework 文件添加到 Xcode 中目标下的 Frameworks, Libraries, and Embedded Content 部分。

  3. 在 Embed 列下,选择 Embed & Sign。

开源第三方库

#

要创建一个同时包含 C/C++/Objective-C 和 Dart 代码的 Flutter plugin,请使用以下说明:

  1. 在你的 plugin 项目中,打开 ios/<myproject>.podspec。

  2. 将原生代码添加到 source_files 字段。

然后,原生代码会静态链接到任何使用此 plugin 的应用程序二进制文件中。

闭源第三方库

#

要创建一个包含 Dart 源代码,但以二进制形式分发 C/C++ 库的 Flutter plugin,请使用以下说明:

  1. 在你的 plugin 项目中,打开 ios/<myproject>.podspec。

  2. 添加一个 vendored_frameworks 字段。请参阅 CocoaPods 示例。

剥离 Symbols 符号

#

创建发布版本时,Xcode 会剥离 Symbols 符号。

  1. 在 Xcode 中,选择 Runner 目标,然后转到 Build Settings > Strip Style。

  2. 从 All Symbols 更改为 Non-Global Symbols。

macOS

#

动态链接器会在应用启动时自动加载动态链接库。它们的组成 Symbols 符号可以使用 DynamicLibrary.process 解析。你也可以使用 DynamicLibrary.open 获取库的句柄来限制 Symbols 符号解析的范围,但目前尚不清楚 Apple 的审核流程如何处理这种情况。

静态链接到应用程序二进制文件中的 Symbols 符号可以使用 DynamicLibrary.executable 或 DynamicLibrary.process 解析。

平台库

#

要链接到平台库,请使用以下说明:

  1. 在 Xcode 中,打开 Runner.xcworkspace。

  2. 选择目标平台。

  3. 在 Linked Frameworks and Libraries 部分中点击 +。

  4. 选择要链接的系统库。

第一方库

#

第一方原生库可以作为源代码或 (已签名的) .framework 文件包含在内。可能也可以包含静态链接的归档文件,但这需要测试。

源代码

#

要直接链接到源代码,请使用以下说明:

  1. 在 Xcode 中,打开 Runner.xcworkspace。

  2. 将 C/C++/Objective-C/Swift 源文件添加到 Xcode 项目中。

  3. 将以下前缀添加到导出的 Symbols 符号声明中,以确保它们对 Dart 可见:

    C/C++/Objective-C:

    objc
    extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
    

    Swift:

    swift
    @_cdecl("myFunctionName")
    

编译后的 (动态) 库

#

要链接到编译后的动态库,请使用以下说明:

  1. 如果存在正确签名的 Framework 文件,请打开 Runner.xcworkspace。

  2. 将 framework 文件添加到 Xcode 中目标下的 Frameworks, Libraries, and Embedded Content 部分。

  3. 在 Embed 列下,选择 Embed & Sign。

编译后的 (动态) 库,闭源

#

要将闭源库添加到 Flutter macOS 桌面 应用中,请使用以下说明:

  1. 按照 Flutter 桌面版的说明创建一个 Flutter 桌面应用。

  2. 在 Xcode 中打开 yourapp/macos/Runner.xcworkspace。

    1. 将你的预编译库 (libyourlibrary.dylib) 拖到 Runner/Frameworks 中。

    2. 点击 Runner 并转到 Build Phases 选项卡。

      1. 将 libyourlibrary.dylib 拖到 Copy Bundle Resources 列表中。

      2. 在 Embed Libraries 下,勾选 Code Sign on Copy。

      3. 在 Link Binary With Libraries 下,将状态设置为 Optional。(我们使用动态链接,无需静态链接。)

    3. 点击 Runner 并转到 General 选项卡。

      1. 将 libyourlibrary.dylib 拖到 Frameworks, Libraries, and Embedded Content 列表中。

      2. 选择 Embed & Sign。

    4. 点击 Runner 并转到 Build Settings 选项卡。

      1. 在 Search Paths 部分中,配置 Library Search Paths 以包含 libyourlibrary.dylib 所在的路径。

  3. 编辑 lib/main.dart。

    1. 使用 DynamicLibrary.open('libyourlibrary.dylib') 动态链接到 Symbols 符号。

    2. 在 widget 中的某个地方调用你的原生函数。

  4. 运行 flutter run 并检查你的原生函数是否被调用。

  5. 运行 flutter build macos 来构建一个独立的发布版本的应用。

剥离 Symbols 符号

#

当创建发布版本时,Xcode 会剥离 Symbols 符号。

  1. 在 Xcode 中,选择 Runner 目标,然后前往 Build Settings > Strip Style。

  2. 从 All Symbols 更改为 Non-Global Symbols。

Android

#

平台库

#

要链接到平台库,请使用以下说明:

  1. 在 Android 文档的 Android NDK Native APIs 列表中找到所需的库。此列表包含稳定的原生 API。

  2. 使用 DynamicLibrary.open 加载库。例如,加载 OpenGL ES (v3):

    dart
    DynamicLibrary.open('libGLES_v3.so');
    

如果文档中有所指示,你可能需要更新应用或 plugin 的 Android manifest 文件。

第一方库

#

以源代码或二进制形式包含原生代码的过程对于应用或 plugin 来说是相同的。

开源第三方库

#

遵循 Android 文档中 将 C 和 C++ 代码添加到你的项目 的说明,以添加原生代码并支持原生代码工具链(无论是 CMake 还是 ndk-build)。

闭源第三方库

#

要创建一个包含 Dart 源代码,但以二进制形式分发 C/C++ 库的 Flutter plugin,请使用以下说明:

  1. 打开你项目的 android/build.gradle 文件。

  2. 将 AAR artifact 添加为依赖项。 不要 将 artifact 包含在你的 Flutter package 中。相反,它应该从仓库(例如 Maven Central)下载。

Android APK 大小(共享对象压缩)

#

Android 指南 通常建议分发未压缩的原生共享对象,因为这实际上节省了设备空间。共享对象可以直接从 APK 加载,而不是在设备上解压到临时位置再加载。 APK 在传输过程中还会额外打包—这就是为什么你应该关注下载大小。

默认情况下,Flutter APK 会压缩 libflutter.so 和 libapp.so,这会导致 APK 文件更小,但设备上的文件更大。要控制原生库是否在安装时压缩和提取,请设置 Android Gradle plugin 的 useLegacyPackaging 选项。有关当前建议,请参阅 Android 指南。

其他资源

#

要了解更多关于 C 互操作性的信息,请查看这些视频: