在 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.executable
或 DynamicLibrary.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 如何靜態連結這個檔案:
-
在 Xcode 中,開啟
Runner.xcworkspace
。 -
新增 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.executable
或 DynamicLibrary.process
解析靜態連結到應用程式二進位制檔案的符號。
平台庫
要連結到平台庫,請按照如下說明:
-
在 Xcode 中,開啟
Runner.xcworkspace
。 -
選擇目標裝置。
-
在 Linked Frameworks and Libraries 中點選 +。
-
選擇要連結的系統庫。
第一方庫
第一方本地庫可以作為原始檔或(已簽名的).framework
檔案被包含在內。它也可能包括靜態連結的檔案,但需要測試。
原始碼
要直接連結到原始碼,請按照如下說明:
-
在 Xcode 中,開啟
Runner.xcworkspace
。 -
新增 C/C++/Objective-C/Swift 原始碼到 Xcode 工程中。
-
將以下字首新增到匯出的符號宣告中,以確保它們對 Dart 可見:
C/C++/Objective-C
extern "C" /* <= C++ only */ __attribute__((visibility("default"))) __attribute__((used))
Swift
@_cdecl("myFunctionName")
已編譯的動態庫
要連結到已編譯過的動態庫,請按照如下說明:
-
如果存在已進行簽名的
Framework
檔案,請開啟Runner.xcworkspace
。 -
新增 framework 檔案到 Embedded Binaries 區域中。
-
同時將其新增到 Xcode 中目標的 Linked Frameworks & Libraries 部分。
已編譯的(動態)庫 (macOS)
要新增一個閉源的函式庫到 Flutter macOS 桌面 應用,請按照如下說明:
-
按照 Flutter 桌面的使用說明來建立 Flutter 桌面應用程式。
-
在 Xcode 中開啟
yourapp/macos/Runner.xcworkspace
。-
拖動您已經預編譯的
libyourlibrary.dylib
到您的Runner/Frameworks
。 -
點選
Runner
然後進入Build Phases
標籤。-
拖動
libyourlibrary.dylib
到Copy Bundle Resources
列表。 -
在
Embed Libararies
下,檢查Code Sign on Copy
。 -
在
Link Binary With Libraries
下,設定狀態為Optional
。(我們使用動態連結,不需要靜態連結)
-
-
點選
Runner
然後進入General
標籤頁。-
拖動
libyourlibrary.dylib
到 Frameworks, Libararies and Embedded Content 列表中。 -
選擇 Embed & Sign。
-
-
點選
Runner
然後進入Build Settings
標籤頁。-
在
Search Paths
部分,配置Library Search Paths
確保libyourlibrary.dylib
的路徑包括在內。
-
-
-
編輯
lib/main.dart
檔案。-
使用
DynamicLibrary.open('libyourlibrary.dylib')
來動態連結符號表。 -
在 widget 的某個地方呼叫您的原生代碼。
-
-
執行
flutter run
然後檢查您的本地方法的呼叫結果。 -
執行
flutter build macos
去建構一個自包含的 release 版本的應用。