Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《Flutter FFI 数组》
- 《Flutter FFI 内存管理》
- 《Flutter FFI Dart Native API》
在前面的章节中,介绍了基础数据类型、字符串、结构体、类、数组等知识点,接下来将介绍一下 FFI 中的内存管理。
在C语言开发过程,内存的申请和回收都是由开发者自己负责的,前面的很多文章都有演示到内存的分配和回收,今天继续来深入学习一下。
1、内存管理介绍
Dart FFI 提供了一些 API,可以让开发者使用 Dart 代码在 Native 中申请内存和释放内存。这些 API 包括 Allocator
、_MallocAllocator
、_CallocAllocator
等。
Allocator
是抽象类,_MallocAllocator
、_CallocAllocator
是它的两个实现类。
Allocator
有两个方法:allocate()
和 free()
,分别用于申请内存和释放内存。Allocator
类的代码如下:
/// Manages memory on the native heap.
abstract class Allocator {
/// Allocates [byteCount] bytes of memory on the native heap.
///
/// If [alignment] is provided, the allocated memory will be at least aligned
/// to [alignment] bytes.
///
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment});
/// Releases memory allocated on the native heap.
///
/// Throws an [ArgumentError] if the memory pointed to by [pointer] cannot be
/// freed.
void free(Pointer pointer);
}
/// Extension on [Allocator] to provide allocation with [NativeType].
extension AllocatorAlloc on Allocator {
/// Allocates `sizeOf<T>() * count` bytes of memory using
/// `allocator.allocate`.
///
/// This extension method must be invoked with a compile-time constant [T].
external Pointer<T> call<T extends NativeType>([int count = 1]);
}
代码说明:
allocate()
:用于申请内存,参数byteCount
表示需要申请的内存的字节数,该函数返回指向该内存的指针,该内存是由 Native 进行分配的;free()
:释放指针所指向的内存。call()
:这是Allocator
的一个扩展函数,让申请内存的写法更简单。
_MallocAllocator
与 _CallocAllocator
都是 Allocator
的子类,区别在于:
_MallocAllocator
申请内存的时候,调用了 C 语言的malloc()
;_CallocAllocator
申请内存的时候,调用了 C 语言的calloc()
;malloc
和calloc
的一个重大区别是:calloc
会自动把内存初始化为0
;
在 Dart 中,已经定义了这两个类的实例,而且是全局的,可以任意调用:
/// Manages memory on the native heap.
///
/// Does not initialize newly allocated memory to zero. Use [calloc] for
/// zero-initialized memory allocation.
///
/// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses
/// `HeapAlloc` and `HeapFree` against the default public heap.
const Allocator malloc = _MallocAllocator();
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero. Use [malloc] for uninitialized
/// memory allocation.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
const Allocator calloc = _CallocAllocator();
代码说明:
- 上面的
malloc
和calloc
是两个实例,可以在任意地方调用;
2、内存分配与释放
前面介绍了使用 Allocator 类进行内存的申请和释放,现在来介绍如何使用 malloc
和 calloc
这两个对象来分配内存、释放内存。
2.1 内存分配与释放
下面的示例中,演示了如何使用 malloc
和 calloc
来申请内存和释放内存:
int size = sizeOf<Int32>();
Pointer<Int32> a = malloc.allocate(size);
Pointer<Int32> b = calloc.allocate(size);
Pointer<Int32> c = calloc.call();
print("a=${a.value}, b=${b.value}, c=${c.value}, sizeof<Int32>=$size");
a.value = 30;
b.value = 27;
c.value = 60;
print("a=${a.value}, b=${b.value}, c=${c.value}");
malloc.free(a);
calloc.free(b);
calloc.free(c);
// 输出结果:
// I/flutter (11797): a = 82, b = 0, sizeof<Int32> = 4
// I/flutter (11797): a = 30, b = 27
代码说明:
- 这里,我们不再需要使用
DynamicLibrary
来加载库,因为malloc
与calloc
的内部已经帮我们做了一这步了; - 调用
allocate()
函数时,需要明确指定byteCount
,这里我们通过sizeOf()
函数来获取Int32
的字节数; - 调用
call()
函数时,不需要指定byteCount
,call()
函数内部已经帮我们调用了sizeOf()
函数了; - 从上面的示例可以看出,
calloc
申请内存之后会初始化为0
,而malloc
则不会; malloc
与calloc
两者的free()
函数的实现都一样。
2.2 数组内存分配与释放
下面的示例中,演示如何通过calloc
来创建数组:
int size = sizeOf<Int32>();
Pointer<Int32> a = malloc.allocate(10 * size);
Pointer<Int32> b = calloc.call(10);
print("a = ${a.asTypedList(10)}");
print("b = ${b.asTypedList(10)}");
for (int i = 0; i < 10; i++) {
a[i] = 10 * i + i;
b[i] = 100 * i + i;
}
print("a = ${a.asTypedList(10)}");
print("b = ${b.asTypedList(10)}");
malloc.free(a);
calloc.free(b);
// 输出结果:
// I/flutter (12223): a = [-1574300648, 111, 243933264, 113, -1637386232, 111, -1637385960, 111, 1049256144, 112]
// I/flutter (12223): b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// I/flutter (12223): a = [0, 11, 22, 33, 44, 55, 66, 77, 88, 99]
// I/flutter (12223): b = [0, 101, 202, 303, 404, 505, 606, 707, 808, 909]
代码说明:
- 上述示例中,再一次证明了,
calloc
会把内存初始为0
,而malloc
不会; - 数组指针可以转为 Dart
List
,这样就可以使用 List 的各种便捷方法:foreach
,where
等;
2.3 结构体内存分配与释放
前面的章节中,介绍了如何在把 C 中的结构映射到 Dart 来使用。
下面的示例,演示如何不使用 C 语言,完全在 Dart 定义结构体、创建结构体、销毁结构体。
//定义一个结构体,表示2D平面上的一点
class Point extends Struct {
@Int32()
external int x;
@Int32()
external int y;
String toDebugString() => "{x=$x, y=$y}";
}
void test() {
//获取Point所占内存大小
int size = sizeOf<Point>();
//创建结构体
Pointer<Point> p1 = calloc.call();
Pointer<Point> p2 = calloc.call();
print("size of point is $size");
print("p1 = ${p1.ref.toDebugString()}");
print("p2 = ${p2.ref.toDebugString()}");
p1.ref.x = 10;
p1.ref.y = 20;
p2.ref.x = 300;
p2.ref.y = 400;
print("p1 = ${p1.ref.toDebugString()}");
print("p2 = ${p2.ref.toDebugString()}");
//销毁结构体
calloc.free(p1);
calloc.free(p2);
}
// 输出结果:
// I/flutter (12223): size of point is 8
// I/flutter (12223): p1 = {x=0, y=0}
// I/flutter (12223): p2 = {x=0, y=0}
// I/flutter (12223): p1 = {x=10, y=20}
// I/flutter (12223): p2 = {x=300, y=400}
代码说明:
- 无
3、自动释放池 —— Arena
有时候需要临时申请内存做一些操作,操作完了就把内存释放掉,但是往往忘记释放内存,又或者 free(a)
写错为 free(b)
,引起内存泄漏。
其实 Dart 已经为我们实现了一个自动释放池,可以应对上述使用场景。
Arena
是 Allocator
的另一个实现类,它与 _MallocAllocator
、_CallocAllocator
最大的不同是:它自动释放由它分配的内存。
Arena
内部默认使用 calloc
来申请内存,每次申请内存的时候,它都会记录下来,后面可以调用它的 releaseAll()
方法全部释放掉。
Arena
的核心代码如下:
class Arena implements Allocator {
//持有一个Allocator,用于实际的内存分配和回收
final Allocator _wrappedAllocator;
//这个List用于记录已分配的内存的指针
final List<Pointer<NativeType>> _managedMemoryPointers = [];
//构造函数,默认使用 calloc
Arena([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
//分配内存
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
//确保当前对象处于使用状态
_ensureInUse();
//启用_wrappedAllocator申请内存
final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
//记录已申请的内存
_managedMemoryPointers.add(p);
return p;
}
//这个是空函数,如果需要释放内存,应调用releaseAll
@override
void free(Pointer<NativeType> pointer) {}
//释放所有内存
void releaseAll({bool reuse = false}) {
//释放后,当前对象是否还能使用
if (!reuse) {
_inUse = false;
}
//释放内存
for (final p in _managedMemoryPointers) {
_wrappedAllocator.free(p);
}
_managedMemoryPointers.clear();
}
}
/// 该方法自动实现了 Arena 的创建和销毁,使用更便捷
R using<R>(R Function(Arena) computation,
[Allocator wrappedAllocator = calloc]) {
final arena = Arena(wrappedAllocator);
bool isAsync = false;
try {
final result = computation(arena);
if (result is Future) {
isAsync = true;
return (result.whenComplete(arena.releaseAll) as R);
}
return result;
} finally {
if (!isAsync) {
arena.releaseAll();
}
}
}
R withZoneArena<R>(R Function() computation,
[Allocator wrappedAllocator = calloc]) {
final arena = Arena(wrappedAllocator);
var arenaHolder = [arena];
bool isAsync = false;
try {
return runZoned(() {
final result = computation();
if (result is Future) {
isAsync = true;
return result.whenComplete(() {
arena.releaseAll();
}) as R;
}
return result;
}, zoneValues: {#_arena: arenaHolder});
} finally {
if (!isAsync) {
arena.releaseAll();
arenaHolder.clear();
}
}
}
代码说明:
free()
函数是空函数,如果需要释放内存,应调用releaseAll()
。using()
函数自动实现了Arena
的创建和销毁,使用更便捷,同样还有withZoneArena()
方法。
下面的示例,演示了如何使用 Arena
完成内存的自动释放。
//创建Arena
Arena arena = Arena();
//使用Arena分配内存
int length = 5;
Pointer<Int32> array = arena.call(length);
Int32List list = array.asTypedList(length);
print("before array=$list");
for (int i = 0; i < length; i++) {
list[i] = i * 100 + i * 5;
}
print("after array=$list");
//回收内存
arena.releaseAll();
// 输出结果:
// I/flutter (12223): before array=[0, 0, 0, 0, 0]
// I/flutter (12223): after array=[0, 105, 210, 315, 420]
代码说明:
- 调用
asTypedList()
之后,并不是创建了一个List
,它的数据还是存储在Pointer<>
所指向的内存;
上面的代码也可以这样写:
using((arena) {
int length = 5;
Pointer<Int32> array = arena.call(length);
Int32List list = array.asTypedList(length);
print("before array=$list");
for (int i = 0; i < length; i++) {
list[i] = i * 100 + i * 5;
}
print("after array=$list");
});
代码说明:
- 上述写法:省去了
Arena
的创建,以及releaseAll
的调用,执行结果是一样的
4、总结
上面介绍了 FFI 的内存管理知识,加上前面章节的知识点,已经可以应付很多开发需求了。如果需要更高级的用法,则可能需要使用 Dart Native API 来解决了,后面的章节中,将会介绍如何使用 Dart Native API 实现 C 异步回调 Dart 等高级用法,欢迎关注。