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
不会;数组指针可以转为 DartList
,这样就可以使用 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 = [];//构造函数,默认使用 callocArena([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@overridevoid 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
完成内存的自动释放。
//创建ArenaArena 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 等高级用法,欢迎关注。
如果觉得《08 Flutter FFI 内存管理》对你有帮助,请点赞、收藏,并留下你的观点哦!