跳到主內容

使用 dart:ffi 進行 C 語言互操作

執行在 Dart 原生平臺上的 Dart 移動、命令列和伺服器應用可以使用 dart:ffi 庫呼叫原生 C API,以及讀、寫、分配和釋放原生記憶體。FFIForeign 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.yamlDart 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 檔案。本節解釋了該檔案的內容。

  1. 匯入 dart:ffi

    dart
    import 'dart:ffi' as ffi;
  2. 匯入 path 庫,該庫將用於儲存動態庫的路徑。

    dart
    import 'dart:io' show Platform, Directory;
    import 'package:path/path.dart' as path;
  3. 使用 C 函式的 FFI 型別簽名建立 typedef。
    要了解 dart:ffi 庫中最常用的型別,請查閱與原生型別互動

    dart
    typedef hello_world_func = ffi.Void Function();
  4. 為呼叫 C 函式時要使用的變數建立 typedef

    dart
    typedef HelloWorld = void Function();
  5. 建立一個變數來儲存動態庫的路徑。

    dart
    final 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',
      );
    }
  6. 開啟包含 C 函式的動態庫。

    dart
    final dylib = ffi.DynamicLibrary.open(libraryPath);
  7. 獲取 C 函式的引用,並將其存入變數。此程式碼使用步驟 2 和 3 中的 typedefs,以及步驟 4 中的動態庫變數。

    dart
    final HelloWorld hello = dylib
        .lookup<ffi.NativeFunction<hello_world_func>>('hello_world')
        .asFunction();
  8. 呼叫 C 函式。

    dart
    hello();

理解 hello_world 示例後,請查閱其他 dart:ffi 示例

打包並載入 C 庫

#

打包/封裝/分發然後載入原生 C 庫的方法取決於平臺和庫型別。

要了解具體方法,請查閱以下頁面和示例。

與原生型別互動

#

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 語言中的函式型別。
OpaqueC 語言中所有不透明型別的超型別。
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 時,在執行 runbuildtest 命令時會呼叫 hook/build.dart 構建鉤子。native_add_app 示例展示了 native_add_library 的用法。

回顧構建鉤子 API 文件

#

以下包的 API 文件可以在這裡找到

提供反饋

#

要提供反饋,請參考這些跟蹤議題