内容

使用 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用于将 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

#

hello.dart 文件 说明了使用 dart:ffi 调用 C 函数的步骤

  1. 导入 dart:ffi
  2. 导入 path 库,用于存储动态库的路径。
  3. 使用 C 函数的 FFI 类型签名创建一个 typedef。
  4. 为调用 C 函数时使用的变量创建一个 typedef。
  5. 创建一个变量来存储动态库的路径。
  6. 打开包含 C 函数的动态库。
  7. 获取对 C 函数的引用,并将其放入变量中。
  8. 调用 C 函数。

以下是每一步的代码。

  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 中定义的 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 类型说明
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 (unsigned long) 类型。
UnsignedLongLong表示 C 中的 unsigned long long 类型。
UnsignedShort表示 C 中的 unsigned short 类型。
WChar表示 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 文档可在 NativeDefaultAssetdart:ffi API 参考中找到。build.dart 脚本的 API 文档可在 package:native_assets_cli API 参考 中找到。

实验选择加入

#

有关如何启用实验和提供反馈的更多信息,请参阅跟踪问题