跳转至正文

使用旧版 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.executableDynamicLibrary.process 加载静态链接库中的 Symbols 符号。

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

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

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

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

创建 FFI plugin

#

要创建名为 native_add 的 FFI plugin,请使用 flutter createplugin_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.executableDynamicLibrary.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.executableDynamicLibrary.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.solibapp.so,这会导致 APK 文件更小,但设备上的文件更大。要控制原生库是否在安装时压缩和提取,请设置 Android Gradle plugin 的 useLegacyPackaging 选项。有关当前建议,请参阅 Android 指南

其他资源

#

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