在 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.executableDynamicLibrary.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 如何静态链接这个文件:

  1. 在 Xcode 中,打开 Runner.xcworkspace

  2. 添加 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.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. 将以下前缀添加到导出的符号声明中,以确保它们对 Dart 可见:

    C/C++/Objective-C

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

    Swift

    @_cdecl("myFunctionName")
    

已编译的动态库

要链接到已编译过的动态库,请按照如下说明:

  1. 如果存在已进行签名的 Framework 文件,请打开 Runner.xcworkspace

  2. 添加 framework 文件到 Embedded Binaries 区域中。

  3. 同时将其添加到 Xcode 中目标的 Linked Frameworks & Libraries 部分。

已编译的(动态)库 (macOS)

要添加一个闭源的库到 Flutter macOS 桌面 应用,请按照如下说明:

  1. 按照 Flutter 桌面的使用说明来创建 Flutter 桌面应用程序。

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

    1. 拖动您已经预编译的 libyourlibrary.dylib 到您的 Runner/Frameworks

    2. 点击 Runner 然后进入 Build Phases 标签。

      1. 拖动 libyourlibrary.dylibCopy Bundle Resources 列表。

      2. Embed Libararies 下,检查 Code Sign on Copy

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

    3. 点击 Runner 然后进入 General 标签页。

      1. 拖动 libyourlibrary.dylibFrameworks, Libararies 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') 来动态链接符号表。

    2. 在 widget 的某个地方调用您的本地代码。

  4. 运行 flutter run 然后检查您的本地方法的调用结果。

  5. 运行 flutter build macos 去构建一个自包含的 release 版本的应用。