內容

使用 dart:ffi 的 C 互操作

Dart Native 平台 上執行的 Dart 行動、命令列和伺服器應用程式可以使用 dart:ffi 函式庫來呼叫原生 C API,並讀取、寫入、配置和取消配置原生記憶體。FFI 代表 外部函式介面。 其他類似功能的名詞包括原生介面語言繫結。

API 文件在 dart:ffi API 參考 中提供。

範例

#

下列範例顯示如何使用 dart:ffi 函式庫

範例說明
hello_world如何呼叫沒有引數且沒有傳回值的 C 函式。
primitives如何呼叫具有引數且傳回值為整數或指標的 C 函式。
structs如何使用結構體將字串傳遞至 C 和從 C 傳回,以及如何處理簡單和複雜的 C 結構體
sqliteDart SDK 存放庫中的範例,附有 迷你教學課程。

hello_world 逐步解說

#

hello_world 範例 具有呼叫 C 函式庫所需的最低限度程式碼。

檔案

#

hello_world 範例有下列檔案

原始碼檔案說明
hello.dart使用 C 函式庫中的 hello_world() 函式的 Dart 檔案。
pubspec.yamlDart pubspec 檔案,其中 SDK 的下限至少為 2.6。
hello_library/hello.h宣告 hello_world() 函式。
hello_library/hello.c匯入 hello.h 並定義 hello_world() 函式的 C 檔案。
hello_library/hello.def一個模組定義檔案,指定在建置 DLL 時所使用的資訊。
hello_library/CMakeLists.txt一個 CMake 建置檔案,用於將 C 程式碼編譯成動態函式庫。

建置 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

#

hello.dart 檔案說明了使用 dart:ffi 來呼叫 C 函式的步驟

  1. 匯入 dart:ffi
  2. 匯入您將用於儲存動態函式庫路徑的路徑函式庫。
  3. 使用 C 函式的 FFI 類型簽章建立一個 typedef。
  4. 為您在呼叫 C 函式時將使用的變數建立一個 typedef。
  5. 建立一個變數來儲存動態函式庫的路徑。
  6. 開啟包含 C 函式的動態函式庫。
  7. 取得對 C 函式的參考,並將其放入變數中。
  8. 呼叫 C 函式。

以下是每個步驟的程式碼。

  1. 匯入 dart:ffi

    dart
    import 'dart:ffi' as ffi;
  2. 匯入您將用於儲存動態函式庫路徑的路徑函式庫。

    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
    var libraryPath = path.join(Directory.current.path, 'hello_library',
        'libhello.so');
    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');
    }
  6. 開啟包含 C 函式的動態函式庫。

    dart
    final dylib = ffi.DynamicLibrary.open(libraryPath);
  7. 取得對 C 函式的參考,並將其放入變數中。此程式碼使用步驟 2 和 3 中定義的 typedef,以及步驟 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 類型說明
陣列項目大小固定的陣列。類型特定陣列的超類型。
指標表示指向原生 C 記憶體的指標。
結構所有 FFI 結構類型的超類型。
聯合所有 FFI 聯合類型的超類型。

純粹標記原生類型

#

下列是與平台無關的原生類型,它們僅用於類型簽章中的標記,且無法在 Dart 程式碼中實例化

Dart 類型說明
布林值表示 C 中的原生布林值。
雙精度表示 C 中的原生 64 位元雙精度。
浮點數表示 C 中的原生 32 位元浮點數。
Int8表示 C 中的原生有號 8 位元整數。
Int16表示 C 中的原生有號 16 位元整數。
Int32表示 C 中的原生有號 32 位元整數。
Int64表示 C 中的原生有號 64 位元整數。
原生函式表示 C 中的函式類型。
不透明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 類型。
長整數表示 C 中的 long int (long) 類型。
長長整數表示 C 中的 long long 類型。
短整數表示 C 中的 short 類型。
有符號字元表示 C 中的 signed char 類型。
大小表示 C 中的 size_t 類型。
無符號整數指標表示 C 中的 uintptr_t 類型。
無符號字元表示 C 中的 unsigned char 類型。
無符號整數表示 C 中的 unsigned int 類型。
無符號長整數表示 C 中的 unsigned long int (unsigned long) 類型。
無符號長長整數表示 C 中的 unsigned long long 類型。
無符號短整數表示 C 中的 unsigned short 類型。
寬字元表示 C 中的 wchar_t 類型。

使用 package:ffigen 產生 FFI 繫結

#

對於大型 API 介面,撰寫與 C 程式碼整合的 Dart 繫結可能會很耗時。為了減輕這個負擔,您可以使用 package:ffigen 繫結產生器,從 C 標頭檔自動建立 FFI 封裝器。

建置和打包原生資產

#

原生資產功能旨在解決與發行依賴原生程式碼的 Dart 套件相關的許多問題。它透過提供統一的掛鉤,與建置 Flutter 和獨立式 Dart 應用程式所涉及的各種建置系統整合,來達成此目的。

原生資產功能旨在讓 Dart 套件依賴和使用原生程式碼變得無縫。

  • 它會 (如果需要) 建置原生程式碼,或使用套件的 build.dart 腳本來取得二進位檔。
  • 它會將 build.dart 腳本回報的原生 Asset 捆綁在一起。
  • 它會透過使用 assetId 的宣告式 @Native<>() extern 函式,在執行階段提供原生資產。

flutter run / flutter builddart run / dart 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 測試。
build.dart用於編譯 src/native_add_library.c 並宣告已編譯資產的 ID 為 package:native_add_library/native_add_library.dart 的指令碼。

當 Dart 或 Flutter 專案依賴 package:native_add_library 時,build.dart 指令碼會在 runbuildtest 指令上自動呼叫。native_add_app 範例展示了 native_add_library 的用法。

原生資產在 Dart FFI 中的 API 文件可在 dart:ffi API 參考中取得,分別為 NativeDefaultAssetbuild.dart 指令碼的 API 文件可在 package:native_assets_cli API 參考 中取得。

實驗選擇加入

#

如需進一步了解如何啟用實驗和提供回饋,請參閱追蹤議題