目录

使用 dart:ffi 进行 C 互操作

Dart Native 平台上运行的 Dart 移动、命令行和服务器应用程序可以使用 dart:ffi 库来调用原生 C API,并读取、写入、分配和释放原生内存。FFI 代表 外部函数接口。类似功能的其他术语包括原生接口语言绑定

API 文档可在 dart:ffi API 参考中找到。

下载示例文件

#

要使用本指南中的示例,请下载完整的 ffi 示例目录。它包含以下示例,展示了如何使用 dart:ffi

示例描述
hello_world如何调用一个没有参数和没有返回值的 C 函数。
primitives如何调用具有 整数或指针参数和返回值的 C 函数。
structs如何使用结构体在 C 中传递和接收 字符串,以及如何处理 简单和复杂的 C 结构体
test_utils所有这些示例的通用测试实用程序。

查看 hello_world 示例

#

hello_world 示例具有调用 C 库所需的最小代码。此示例可以在您在上一节中下载的 samples/ffi 中找到。

文件

#

hello_world 示例具有以下文件

源文件描述
hello.dart一个 Dart 文件,它使用 C 库中的 hello_world() 函数。
pubspec.yamlDart pubspec 文件,其 SDK 下限为 3.4。
hello_library/hello.h声明 hello_world() 函数。
hello_library/hello.c一个 C 文件,它导入 hello.h 并定义 hello_world() 函数。
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

#

要了解如何使用 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
    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 中的 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 库的方法取决于平台和库类型。

要了解如何操作,请查阅以下页面和示例。

  • 适用于 Android 应用程序的 Flutter dart:ffi
  • 适用于 iOS 应用程序的 Flutter dart:ffi
  • 适用于 macOS 应用程序的 Flutter dart:ffi
  • dart:ffi 示例

与原生类型交互

#

dart:ffi 库提供了多种类型,这些类型实现了 NativeType 并表示 C 中的原生类型。您可以实例化一些原生类型。其他一些原生类型只能用作类型签名中的标记。

可以实例化这些类型签名标记

#

以下原生类型可以用作类型签名中的标记。它们或其子类型可以在 Dart 代码中实例化。

Dart 类型描述
Array固定大小的项数组。特定类型数组的超类型。
Pointer表示指向原生 C 内存的指针。
Struct所有 FFI 结构体类型的超类型。
Union所有 FFI 联合类型的超类型。

仅作为类型签名标记

#

以下列表显示了作为类型签名中标记的与平台无关的原生类型。它们不能在 Dart 代码中实例化。

Dart 类型描述
Bool表示 C 中的原生布尔值。
Double表示 C 中的原生 64 位双精度浮点数。
Float表示 C 语言中的原生 32 位浮点数。
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 intlong)类型。
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 包的发布相关的许多问题。它通过为集成到构建 Flutter 和独立 Dart 应用程序的各种构建系统提供统一的钩子来实现这一点。

此特性应该简化 Dart 包依赖和使用原生代码的方式。原生资产应该提供以下好处

  • 使用包的 hook/build.dart 构建钩子构建原生代码或获取二进制文件。
  • 捆绑 build.dart 构建钩子报告的原生 Asset
  • 通过使用 assetId 的声明式 @Native<>() extern 函数,使原生资产在运行时可用。

当您选择加入原生实验时,flutter (run|build)dart (run|build) 命令会使用 Dart 代码构建并捆绑原生代码。

查看 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 文档

选择加入实验

#

要了解如何启用实验并提供反馈,请查阅以下跟踪问题