Dart 如何直接调用 C 语言库

理解 Dart 如何通过外部函数接口(dart:ffi)调用 C 函数是项强大技能。它能让你利用现有 C/C++ 库、提升计算密集型任务性能,并与原生系统 API 交互。

让我们通过一个简单而详细的示例逐步拆解。

整体原理:工作机制

该过程包含三个主要阶段:

  1. C 端 :编写 C 函数并编译为动态库 (Linux 的 .so 文件,macOS 的 .dylib,Windows 的 .dll)。这是独立的编译代码包,其他程序可在运行时加载使用。
  2. Dart 端 (dart:ffi) :使用 dart:ffi 库: a. 将动态库加载到 Dart 应用 b. 通过函数名(符号)查找目标 C 函数 c. 告知 Dart 确切的函数签名(参数和返回类型)以确保安全调用
  3. 执行阶段 :像调用普通 Dart 函数一样调用 C 函数。dart:ffi 在底层处理转换,向 C 函数传递数据并将结果返回给 Dart。

分步示例:实现 add 函数

让我们创建计算两数之和的 C 函数,并从 Dart 调用它。

步骤 1:编写 C 代码

创建 C 源文件 native_add.c

c 复制代码
// native_add.c

// 导出函数使其对动态库加载器可见
// Windows 可能需要 __declspec(dllexport)
// Linux/macOS 默认导出所有函数,但显式声明更规范
#if defined(_WIN32)
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif

// 这是要从 Dart 调用的 C 函数
// 接收两个 32 位整数并返回它们的和
EXPORT int add(int a, int b) {
    return a + b;
}

说明:

  • #if defined...:跨平台预处理指令。确保在 Windows 显式标记 DLL 导出函数,Linux/macOS 中 EXPORT 为空标记。
  • int add(int a, int b):标准 C 函数。需使用大小固定的 C 类型。现代平台中 int 通常为 32 位整数,完美对应 Dart FFI 的 Int32

步骤 2:编译 C 代码为动态库

此步骤与平台相关。需使用 GCC/Clang(Linux/macOS)或 Visual Studio/MinGW(Windows)。

native_add.c 所在目录执行对应命令:

  • Linux:

    bash 复制代码
    gcc -shared -o libnative_add.so -fPIC native_add.c
  • macOS:

    bash 复制代码
    gcc -shared -o libnative_add.dylib native_add.c
  • Windows(使用 GCC/MinGW):

    bash 复制代码
    gcc -shared -o native_add.dll native_add.c

编译器参数说明:

  • -shared:指示编译器生成共享库而非可执行文件
  • -o libnative_add.so/.dylib/.dll:指定输出文件名(Linux/macOS 通常加 lib 前缀)
  • -fPIC:生成位置无关代码(Position-Independent Code),共享库的核心要求

编译后目录会生成新文件(libnative_add.so/libnative_add.dylib/native_add.dll),这就是 Dart 要加载的编译后 C 代码

步骤 3:编写调用 C 函数的 Dart 代码

在相同目录创建 main.dart

dart 复制代码
import 'dart:io';    // 获取平台信息
import 'dart:ffi';   // 外部函数接口库

// --- 步骤 1:定义函数签名类型 ---

// 定义 C 函数签名
// 必须与 C 代码的 `int add(int a, int b)` 匹配
// `Int32` 是 `dart:ffi` 中兼容 C 的 32 位整数
typedef CAddFunc = Int32 Function(Int32 a, Int32 b);

// 定义 Dart 函数签名
// 这是 Dart 代码中使用的友好接口
// `int` 是 Dart 原生整数类型
typedef DartAddFunc = int Function(int a, int b);

void main() {
  // --- 步骤 2:加载动态库 ---

  // 构建动态库路径(需与脚本同目录或提供完整路径)
  var dylibPath = Platform.isWindows ? 'native_add.dll' : 'libnative_add.so';
  if (Platform.isMacOS) {
    // macOS 需要特定路径格式
    // 通常置于可执行文件同级目录
    dylibPath = 'libnative_add.dylib';
  }

  // 加载动态库
  final dylib = DynamicLibrary.open(dylibPath);


  // --- 步骤 3:查找并转换 C 函数 ---

  // 在库中查找函数符号 'add'
  // 使用 C 函数签名类型定义
  final addPointer = dylib.lookup<NativeFunction<CAddFunc>>('add');

  // 将 C 函数指针转换为可调用的 Dart 函数
  // 使用 Dart 函数签名类型定义
  final add = addPointer.asFunction<DartAddFunc>();


  // --- 步骤 4:调用函数 ---

  // 现在可以像调用 Dart 函数一样调用 C 函数!
  final result = add(10, 25);

  print('C 函数返回结果: $result');

  // 再次调用示例
  print('C 函数再次返回: ${add(99, 1)}');
}

运行 Dart 程序:

bash 复制代码
dart run main.dart

输出结果:

复制代码
C 函数返回结果: 35
C 函数再次返回: 100

Dart 代码深度解析

步骤 1:类型定义(函数蓝图)

dart 复制代码
typedef CAddFunc = Int32 Function(Int32 a, Int32 b);
typedef DartAddFunc = int Function(int a, int b);

这是确保正确性的核心:

  • CAddFunc:描述 C 函数的原生内存布局 。必须使用 dart:ffi 类型(Int32, Float, Pointer 等),因其内存大小和表示与 C 完全一致。Dart 的 int 可能是 64 位,会导致不匹配。
  • DartAddFunc:定义 Dart 环境 中的函数接口。可使用便捷的 Dart 类型(如 int/double)。dart:ffi 会自动处理 intInt32 的转换。分离这两个类型定义使代码更清晰。

步骤 2:加载动态库

dart 复制代码
final dylib = DynamicLibrary.open(dylibPath);

程序要求操作系统查找并加载动态库文件(.dll/.so/.dylib)。若文件未找到会抛出异常。

步骤 3:查找与转换

dart 复制代码
final addPointer = dylib.lookup<NativeFunction<CAddFunc>>('add');
final add = addPointer.asFunction<DartAddFunc>();

两步关键操作:

  1. 查找符号 :在加载库中搜索名为 add 的函数(导出符号)。泛型 <NativeFunction<CAddFunc>> 指示 Dart:"查找的对象是原生函数指针,其内存布局由 CAddFunc 描述"。结果 addPointer 是内存中函数的原始指针,不可直接调用。
  2. 函数转换.asFunction<...>() 是桥梁。它将原始函数指针包装成 Dart 可调用对象。泛型 <DartAddFunc> 指定最终 Dart 函数的形态。dart:ffi 生成高度优化的"桥接代码",在 Dart VM 和 C 函数间跳转,按签名转换参数和返回值。

步骤 4:函数调用

dart 复制代码
final result = add(10, 25);

经过转换后,add 成为一等公民的 Dart 函数。可像普通 Dart 代码一样调用、传参,这正是 dart:ffi 的精妙之处。

核心类型映射表

C 类型 dart:ffi 类型 (C 签名用) Dart 类型 (Dart 签名用)
int32_t, int Int32 int
int64_t, long long Int64 int
float Float double
double Double double
char* (UTF-8 字符串) Pointer<Utf8> String (需辅助方法)
void* Pointer<Void> Pointer<Void>
struct MyStruct { ... } class MyStruct extends Struct { ... } MyStruct
相关推荐
张风捷特烈2 小时前
Flutter 百题斩#17 | SDK 组件数据入库 - sqlite
android·前端·flutter
王二蛋与他的张大花20 小时前
深入解析与彻底解决 Android 集成 Flutter Boost 时页面闪烁问题
flutter
猪哥帅过吴彦祖1 天前
Flutter 与原生平台之间的通信
flutter
恋猫de小郭1 天前
聊聊 Flutter 在 iOS 真机 Debug 运行出现 Timed out *** to update 的问题
android·前端·flutter
火柴就是我2 天前
每日见闻之Flutter 怎么设置全局字体
android·flutter
江上清风山间明月2 天前
Flutter和Kotlin的对比
开发语言·flutter·kotlin
亿刀3 天前
为什么要学习Flutter编译过程
android·flutter
技术蔡蔡3 天前
Flutter和Firebae与单人聊天的简单实现(Firebase Realtime Database)
flutter·firebase
节省钱3 天前
【Flutter】深入理解 Provider:不仅仅是Consumer
开发语言·前端·flutter·前端框架