Dart FFI 内存管理:用 using + Arena 替代嵌套 try-finally

在使用 Dart FFI 调用 Windows API 时,经常需要手动申请 Native 内存。

例如调用 API 获取物理显示器信息,需要先分配一块内存保存返回值:

ini 复制代码
final countPointer = calloc<ffi.Uint32>();

使用完成后,再手动释放:

arduino 复制代码
try {
  // 调用 Native API
} finally {
  calloc.free(countPointer);
}

当一个方法需要申请多块 Native 内存时,代码很快会变成多个嵌套的 try-finally。

业务代码夹在内存申请和释放之间,代码变长,资源生命周期也不容易看清。

对于这种临时 Native 内存,可以使用 package:ffi 提供的 using 和 Arena。

使用 using + Arena 管理内存

Arena 可以理解成一个 Native 内存作用域。

在 using 中通过 arena 分配的内存,会在作用域结束时统一释放。

例如:

kotlin 复制代码
import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';

final result = using((Arena arena) {
  final pointer = arena<ffi.Uint32>();

  // 使用 pointer
  return pointer.value;
});

不需要再为 pointer 编写:

arduino 复制代码
try {
  // ...
} finally {
  calloc.free(pointer);
}

当 using 作用域结束后,Arena 会处理在这个作用域中分配的内存。

多块 Native 内存一起管理

实际使用 FFI 时,一个 API 调用可能需要多块内存。

例如获取物理显示器信息:

arduino 复制代码
final countPointer = calloc<ffi.Uint32>();

try {
  // 获取数量

  final physicalArray = calloc<PhysicalMonitor>(count);

  try {
    // 获取物理显示器

    // 使用 physicalArray
  } finally {
    calloc.free(physicalArray);
  }
} finally {
  calloc.free(countPointer);
}

这里的问题不是释放本身,而是资源管理代码开始占据业务代码的位置。

使用 Arena 后,可以把它改成:

ini 复制代码
return using((Arena arena) {
  final countPointer = arena<ffi.Uint32>();

  final countResult = getNumberOfPhysicalMonitors(
    hMonitor,
    countPointer,
  );

  if (countResult == 0) {
    throw StateError(
      '获取物理监视器数量失败,Win32 错误 ${getLastError()}.',
    );
  }

  final count = countPointer.value;

  if (count == 0) {
    throw StateError(
      '监视器句柄 $hMonitor 没有关联的物理监视器.',
    );
  }

  final physicalArray =
      arena<PhysicalMonitor>(count);

  final getResult = getPhysicalMonitorsFromHMonitor(
    hMonitor,
    count,
    physicalArray,
  );

  if (getResult == 0) {
    throw StateError(
      '获取物理监视器信息失败,Win32 错误 ${getLastError()}.',
    );
  }

  final handles = <int>[];

  for (int index = 0; index < count; index++) {
    final monitor = (physicalArray + index).ref;
    handles.add(monitor.hPhysicalMonitor);
  }

  return handles;
});

这里申请了两块 Native 内存:

ini 复制代码
final countPointer = arena<ffi.Uint32>();

final physicalArray =
    arena<PhysicalMonitor>(count);

但不需要分别释放。

离开 using 作用域后,Arena 会统一处理这些内存。

using 的价值是什么?

原来的代码需要关注:

csharp 复制代码
申请内存
    ↓
try
    ↓
调用 API
    ↓
finally
    ↓
释放内存

申请多块内存后,还需要考虑多个 try-finally 的嵌套关系。

使用 Arena 后,代码变成:

arduino 复制代码
进入 using
    ↓
创建 Arena
    ↓
分配 Native 内存
    ↓
调用 API
    ↓
读取结果
    ↓
离开 using
    ↓
统一释放

资源生命周期从多个 try-finally 收缩成一个作用域。

这也是 Arena 更适合临时 FFI 内存的原因。

Arena 适合临时内存

并不是所有 Native 资源都应该交给 Arena。

例如:

scss 复制代码
using((Arena arena) {
  final pointer = arena<SomeStruct>();

  // 调用 API
});

如果这个指针只在当前方法中使用,使用 Arena 很合适。

但如果需要把 Native 指针保存到一个 Dart 对象中,让它跨越当前方法继续存在,就不能简单地放进当前 Arena。

这时需要重新设计资源的生命周期。

NativeFinalizer 是另一种思路

如果需要把 Native 资源封装成 Dart 对象,可以考虑 NativeFinalizer。

例如:

markdown 复制代码
Dart 对象
    ↓
持有 Native 指针 / 句柄
    ↓
对象生命周期结束
    ↓
NativeFinalizer
    ↓
调用 Native 释放函数

这种方式适合生命周期更长的 Native 资源。

因此,两种方案解决的问题不同:

方式 适合场景
using + Arena 方法内部的临时 Native 内存
NativeFinalizer 与 Dart 对象生命周期绑定的 Native 资源

对于一次 FFI 调用中临时申请的指针,没有必要为了它引入更复杂的对象生命周期管理。

还要注意 Native 句柄和 Native 内存不是一回事

使用 Arena 只能解决 Arena 管理的 Native 内存。

如果 Windows API 返回的是需要显式销毁的系统句柄,仍然需要调用对应的 Windows API。

例如某些 API 返回的资源需要:

markdown 复制代码
Native 内存
    ↓
arena 自动释放

Windows 句柄
    ↓
调用对应 Destroy / Close API

不能因为使用了 Arena,就认为所有 Native 资源都会自动释放。

因此,在使用 FFI 时,需要区分:

  • Dart 对象
  • Native 堆内存
  • Native 指针
  • Windows 系统句柄
  • API 特定的资源释放函数

它们的生命周期并不相同。

相关推荐
guslegend1 小时前
脚手架入门:必要性、核心功能与执行原理
前端·架构·node.js·脚手架·前端工程化
律宏阔1 小时前
Flutter 调用 Go:从 c-shared + ffigen 到 @Native + Native Assets 踩坑记录
前端·flutter
LEE1 小时前
前端转型全栈 05:SQL 与迁移,AI 写的 SQL 怎么安全上线
前端·后端·ai编程
用户15741568165341 小时前
macOS 打包体积异常的排查实录
前端
数据掘金1 小时前
小程序埋点方案上线前要检查哪些点?我用一张检查清单过了二十多项
前端
Hooray1 小时前
后台管理框架存活率大调查(2026版)
前端
律宏阔1 小时前
Dart FFI 回调无法使用局部变量?使用 NativeCallable.isolateLocal
前端·flutter
hai_android1 小时前
一个公式看懂动态跨表查找:P8 + VLOOKUP
前端·javascript·vue.js
行者全栈架构师1 小时前
【鸿蒙心迹】从 TypeScript 迁移到 ArkTS——10 个编译报错逐个拆解(HarmonyOS 7.x)
前端·人工智能·开源