Skip to main content

使用舊版 FFI 插件範本繫結至原生程式碼

使用舊版 plugin_ffi 範本及 dart:ffi,在 Flutter 插件或應用程式中 繫結至原生 C 程式碼。

Flutter 行動端與桌面端應用程式可使用 dart:ffi 函式庫呼叫原生 C API。 FFI 代表外部函式介面 (foreign function interface)。 類似功能的其他術語包括 native interface 及 language bindings。

在函式庫或程式使用 FFI 函式庫繫結至原生程式碼之前, 你必須確保原生程式碼已載入,且其符號 (symbols) 對 Dart 是可見的。 本頁著重於在 Flutter 插件或應用程式中編譯、封裝及載入原生程式碼。

本教學示範如何在 Flutter 插件中打包 C/C++ 原始碼, 並使用 Dart FFI 函式庫繫結至這些原始碼。 在本逐步說明中,你將建立一個實作 32 位元加法的 C 函式, 然後透過名為 native_add 的 Dart 插件將其對外公開。

動態連結與靜態連結

#

原生函式庫可以動態或靜態方式連結至應用程式。 靜態連結函式庫會嵌入至應用程式的可執行映像中, 並於應用程式啟動時載入。

靜態連結函式庫的符號可使用 DynamicLibrary.executable 或 DynamicLibrary.process 載入。

相對地,動態連結函式庫會以獨立檔案或資料夾的形式 散布於應用程式中,並依需求載入。散布格式依平台而異:

  • 在 Android 上,動態連結函式庫以一組 .so(ELF)檔案的形式散布, 每種架構各一個檔案。僅支援動態函式庫, 因為主執行檔為 JVM,Flutter 不會靜態連結至 JVM。
  • 在 iOS 及 macOS 上,動態連結函式庫以 .framework 資料夾形式散布。

動態連結函式庫可使用 DynamicLibrary.open 載入至 Dart 中。

建立 FFI 插件

#

若要建立名為 native_add 的 FFI 插件, 請使用 flutter create 搭配 plugin_ffi 範本:

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

這會在 native_add/src 中建立含有 C/C++ 原始碼的插件。 這些原始碼由各作業系統建置資料夾中的原生建置檔案負責建置。

FFI 函式庫只能繫結至 C 符號, 因此在 C++ 中這些符號需標記為 extern "C"。

你還應加入屬性,以表明這些符號由 Dart 參照, 防止連結器在連結時期最佳化過程中捨棄這些符號: __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

#

動態連結器會在應用程式啟動時自動載入動態連結函式庫。 其組成符號可使用 DynamicLibrary.process 解析。 你也可以使用 DynamicLibrary.open 取得函式庫的控制代碼 以限制符號解析的範圍,但目前尚不清楚 Apple 審核流程如何處理此情況。

靜態連結至應用程式二進位檔的符號 可使用 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. 在匯出的符號宣告前加入以下前綴, 以確保其對 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 插件, 請依照以下步驟操作:

  1. 在你的插件專案中,開啟 ios/<myproject>.podspec。
  2. 將原生程式碼加入 source_files 欄位。

原生程式碼隨後會靜態連結至 任何使用此插件之應用程式的應用程式二進位檔中。

閉源第三方函式庫

#

若要建立包含 Dart 原始碼但以二進位形式散布 C/C++ 函式庫的 Flutter 插件, 請依照以下步驟操作:

  1. 在你的插件專案中,開啟 ios/<myproject>.podspec。
  2. 加入 vendored_frameworks 欄位。 請參閱 CocoaPods 範例。

移除符號

#

建立正式版建置時,Xcode 會移除符號。

  1. 在 Xcode 中,選取 Runner 目標, 然後前往 Build Settings > Strip Style。
  2. 將設定從 All Symbols 改為 Non-Global Symbols。

macOS

#

動態連結器會在應用程式啟動時自動載入動態連結函式庫。 其組成符號可使用 DynamicLibrary.process 解析。 你也可以使用 DynamicLibrary.open 取得函式庫的控制代碼 以限制符號解析的範圍,但目前尚不清楚 Apple 審核流程如何處理此情況。

靜態連結至應用程式二進位檔的符號 可使用 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. 在匯出的符號宣告前加入以下前綴, 以確保其對 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 Desktop 應用程式, 請依照以下步驟操作:

  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') 動態連結至符號。
    2. 在元件 (Widget) 中的某處呼叫你的原生函式。
  4. 執行 flutter run 並確認你的原生函式已被呼叫。
  5. 執行 flutter build macos 以建置 可獨立運作的正式版應用程式。

移除符號

#

建立正式版建置時,Xcode 會移除符號。

  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');
    

若文件中有指示,你可能需要更新 應用程式或插件的 Android manifest 檔案。

第一方函式庫

#

在應用程式或插件中以原始碼或二進位形式加入原生程式碼的流程相同。

開放原始碼第三方函式庫

#

依照 Android 文件中的將 C 和 C++ 程式碼加入專案 說明,加入原生程式碼並支援原生程式碼工具鏈(CMake 或 ndk-build)。

閉源第三方函式庫

#

若要建立包含 Dart 原始碼但以二進位形式散布 C/C++ 函式庫的 Flutter 插件, 請依照以下步驟操作:

  1. 開啟你專案的 android/build.gradle 檔案。
  2. 將 AAR artifact 加入為相依套件。 請勿將該 artifact 包含在你的 Flutter 套件中。 它應從如 Maven Central 等儲存庫下載。

Android APK 大小(共用物件壓縮)

#

Android 準則通常建議 以未壓縮方式散布原生共用物件, 因為這實際上可節省裝置儲存空間。 共用物件可直接從 APK 載入, 無需先解壓縮至裝置上的暫存位置再載入。 APK 在傳輸時還會另外壓縮—這就是 為什麼你應該關注下載大小的原因。

預設情況下,Flutter APK 會壓縮 libflutter.so 和 libapp.so, 這會使 APK 較小,但裝置上的檔案較大。 若要控制原生函式庫是否在安裝時以壓縮方式儲存並解壓縮, 請設定 Android Gradle 插件的 useLegacyPackaging 選項。 目前的建議請參閱 Android 準則。

其他資源

#

若要深入了解 C 互通,請參閱以下影片: