使用 dart:ffi 進行 C 語言互操作
執行在 Dart 原生平臺上的 Dart 移動、命令列和伺服器應用可以使用 dart:ffi 庫呼叫原生 C API,以及讀、寫、分配和釋放原生記憶體。FFI 是 Foreign Function Interface 的縮寫,意為外部函式介面。類似功能的其他術語包括原生介面和語言繫結。
API 文件可在dart:ffi API 參考中查閱。
下載示例檔案
#要使用本指南中的示例,請下載完整的 ffi 示例目錄。它包括以下示例,展示如何使用 dart:ffi 庫
| 示例 | 描述 |
|---|---|
| hello_world | 如何呼叫沒有引數和返回值的 C 函式。 |
| primitives | 如何呼叫具有整型或指標引數和返回值的 C 函式。 |
| structs | 如何使用結構體在 C 語言中傳遞字串以及處理簡單和複雜的 C 結構體。 |
| test_utils | 這些示例通用的測試工具。 |
回顧 hello_world 示例
#要了解如何使用 dart:ffi 庫呼叫 C 函式,請回顧 hello.dart 檔案。本節解釋了該檔案的內容。
檔案
#hello_world 示例包含以下檔案
| 原始檔 | 描述 |
|---|---|
hello.dart | 一個使用 C 庫中 hello_world() 函式的 Dart 檔案。 |
pubspec.yaml | Dart pubspec 檔案,SDK 下限為 3.4。 |
hello_library/hello.h | 宣告 hello_world() 函式。 |
hello_library/hello.c | 一個匯入 hello.h 並定義 hello_world() 函式的 C 檔案。 |
hello_library/hello.def | 一個模組定義檔案,用於指定構建 DLL 時使用的資訊。 |
hello_library/CMakeLists.txt | 一個用於將 C 程式碼編譯成動態庫的 CMake 構建檔案。 |
構建 C 庫會建立幾個檔案,包括名為 libhello.dylib (macOS)、libhello.dll (Windows) 或 libhello.so (Linux) 的動態庫檔案。
構建和執行
#構建動態庫並執行 Dart 應用的命令序列類似於以下內容。
cd hello_library
cmake .
...
make
...
cd ..
dart pub get
dart run hello.dart
Hello World利用 dart:ffi
#要了解如何使用 dart:ffi 庫呼叫 C 函式,請回顧 hello.dart 檔案。本節解釋了該檔案的內容。
匯入
dart:ffi。dartimport 'dart:ffi' as ffi;匯入 path 庫,該庫將用於儲存動態庫的路徑。
dartimport 'dart:io' show Platform, Directory; import 'package:path/path.dart' as path;使用 C 函式的 FFI 型別簽名建立 typedef。
要了解dart:ffi庫中最常用的型別,請查閱與原生型別互動。darttypedef hello_world_func = ffi.Void Function();為呼叫 C 函式時要使用的變數建立
typedef。darttypedef HelloWorld = void Function();建立一個變數來儲存動態庫的路徑。
dartfinal String libraryPath; if (Platform.isMacOS) { libraryPath = path.join( Directory.current.path, 'hello_library', 'libhello.dylib', ); } else if (Platform.isWindows) { libraryPath = path.join( Directory.current.path, 'hello_library', 'Debug', 'hello.dll', ); } else { libraryPath = path.join( Directory.current.path, 'hello_library', 'libhello.so', ); }開啟包含 C 函式的動態庫。
dartfinal dylib = ffi.DynamicLibrary.open(libraryPath);獲取 C 函式的引用,並將其存入變數。此程式碼使用步驟 2 和 3 中的
typedefs,以及步驟 4 中的動態庫變數。dartfinal HelloWorld hello = dylib .lookup<ffi.NativeFunction<hello_world_func>>('hello_world') .asFunction();呼叫 C 函式。
darthello();
理解 hello_world 示例後,請查閱其他 dart:ffi 示例。
打包並載入 C 庫
#打包/封裝/分發然後載入原生 C 庫的方法取決於平臺和庫型別。
要了解具體方法,請查閱以下頁面和示例。
- Flutter
dart:ffi用於 Android 應用 - Flutter
dart:ffi用於 iOS 應用 - Flutter
dart:ffi用於 macOS 應用 dart:ffi示例
與原生型別互動
#dart:ffi 庫提供了多種實現 NativeType 並表示 C 語言原生型別的型別。您可以例項化某些原生型別。另一些原生型別只能用作型別簽名中的標記。
可以例項化這些型別簽名標記
#以下原生型別可用作型別簽名中的標記。它們或其子型別可以在 Dart 程式碼中例項化。
| Dart 型別 | 描述 |
|---|---|
| Array | 固定大小的條目陣列。特定型別陣列的超型別。 |
| Pointer | 表示指向原生 C 記憶體的指標。 |
| Struct | 所有 FFI 結構體型別的超型別。 |
| Union | 所有 FFI 聯合體型別的超型別。 |
僅作為型別簽名標記
#以下列表顯示了哪些平臺無關的原生型別可用作型別簽名中的標記。它們不能在 Dart 程式碼中例項化。
| Dart 型別 | 描述 |
|---|---|
| Bool | 表示 C 語言中的原生 bool 型別。 |
| Double | 表示 C 語言中的原生 64 位 double 型別。 |
| Float | 表示 C 語言中的原生 32 位 float 型別。 |
| Int8 | 表示 C 語言中的原生帶符號 8 位整數。 |
| Int16 | 表示 C 語言中的原生帶符號 16 位整數。 |
| Int32 | 表示 C 語言中的原生帶符號 32 位整數。 |
| Int64 | 表示 C 語言中的原生帶符號 64 位整數。 |
| NativeFunction | 表示 C 語言中的函式型別。 |
| Opaque | C 語言中所有不透明型別的超型別。 |
| Uint8 | 表示 C 語言中的原生無符號 8 位整數。 |
| Uint16 | 表示 C 語言中的原生無符號 16 位整數。 |
| Uint32 | 表示 C 語言中的原生無符號 32 位整數。 |
| Uint64 | 表示 C 語言中的原生無符號 64 位整數。 |
| Void | 表示 C 語言中的 void 型別。 |
還有許多特定於 ABI 的標記原生型別,它們擴充套件自 AbiSpecificInteger。要了解這些型別在特定平臺上的對映方式,請查閱下表中連結的 API 文件。
| Dart 型別 | 描述 |
|---|---|
| AbiSpecificInteger | 所有 ABI 特定整數型別的超型別。 |
| Int | 表示 C 語言中的 int 型別。 |
| IntPtr | 表示 C 語言中的 intptr_t 型別。 |
| Long | 表示 C 語言中的 long int (即 long) 型別。 |
| LongLong | 表示 C 語言中的 long long 型別。 |
| Short | 表示 C 語言中的 short 型別。 |
| SignedChar | 表示 C 語言中的 signed char 型別。 |
| Size | 表示 C 語言中的 size_t 型別。 |
| UintPtr | 表示 C 語言中的 uintptr_t 型別。 |
| UnsignedChar | 表示 C 語言中的 unsigned char 型別。 |
| UnsignedInt | 表示 C 語言中的 unsigned int 型別。 |
| UnsignedLong | 表示 C 語言中的 unsigned long int 型別。 |
| UnsignedLongLong | 表示 C 語言中的 unsigned long long 型別。 |
| UnsignedShort | 表示 C 語言中的 unsigned short 型別。 |
| WChar | 表示 C 語言中的 wchar_t 型別。 |
使用 package:ffigen 生成 FFI 繫結
#對於大型 API 介面,手動編寫與 C 程式碼整合的 Dart 繫結可能非常耗時。要讓 Dart 從 C 標頭檔案建立 FFI 包裝器,請使用 package:ffigen 繫結生成器。
構建並打包原生程式碼
#Dart 構建鉤子(以前稱為原生資產)使包除了 Dart 原始碼外,還能包含更多內容。現在,包可以包含原生程式碼資產,這些資產會被透明地構建、打包並在執行時可用。
此功能簡化了 Dart 包依賴和使用原生程式碼的方式
- 使用包的構建鉤子(位於
hook/build.dart中)構建原生程式碼或獲取二進位制檔案。 - Dart 和 Flutter 會打包構建鉤子報告的
CodeAsset。 - 透過使用
assetId的宣告式@Native<>() extern函式在執行時訪問程式碼資產。
Flutter 和獨立的 Dart 會自動打包應用使用的所有包的原生程式碼,並在執行時使其可用。這適用於 flutter (run|build) 和 dart (run|build) 命令。
回顧 native_add_library 示例
#native_add_library 示例包含了在 Dart 包中構建和打包 C 程式碼所需的最低程式碼。
該示例包含以下檔案
| 原始檔 | 描述 |
|---|---|
src/native_add_library.c | 包含 add 函式程式碼的 C 檔案。 |
lib/native_add_library.dart | 透過 FFI 呼叫資產 package:native_add_library/native_add_library.dart 中 C 函式 add 的 Dart 檔案。(注意,資產 ID 預設使用庫 URI。) |
test/native_add_library_test.dart | 一個使用原生程式碼的 Dart 測試。 |
hook/build.dart | 一個構建鉤子,用於編譯 src/native_add_library.c 並宣告 ID 為 package:native_add_library/native_add_library.dart 的已編譯資產。 |
當 Dart 或 Flutter 專案依賴於 package:native_add_library 時,在執行 run、build 和 test 命令時會呼叫 hook/build.dart 構建鉤子。native_add_app 示例展示了 native_add_library 的用法。
回顧構建鉤子 API 文件
#以下包的 API 文件可以在這裡找到
- 要了解 Dart FFI 中對程式碼資產的支援,請查閱
dart:ffiAPI 參考中的Native和DefaultAsset。 - 要了解
hook/build.dart構建鉤子,請查閱package:hooksAPI 參考。
提供反饋
#要提供反饋,請參考這些跟蹤議題