在 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 版本的應用。