08、Flutter FFI 内存管理

 
Flutter FFI 学习笔记系列

  1. 《Flutter FFI 最简示例》
  2. 《Flutter FFI 基础数据类型》
  3. 《Flutter FFI 函数》
  4. 《Flutter FFI 字符串》
  5. 《Flutter FFI 结构体》
  6. 《Flutter FFI 类》
  7. 《Flutter FFI 数组》
  8. 《Flutter FFI 内存管理》
  9. 《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()
  • malloccalloc 的一个重大区别是: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();

  代码说明

  • 上面的 malloccalloc 是两个实例,可以在任意地方调用;
     

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 来加载库,因为 malloccalloc 的内部已经帮我们做了一这步了;
  • 调用 allocate() 函数时,需要明确指定 byteCount,这里我们通过 sizeOf() 函数来获取 Int32 的字节数;
  • 调用 call() 函数时,不需要指定 byteCountcall() 函数内部已经帮我们调用了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 已经为我们实现了一个自动释放池,可以应对上述使用场景。
  
  ArenaAllocator 的另一个实现类,它与 _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 等高级用法,欢迎关注。

  
 

猜你喜欢

转载自blog.csdn.net/eieihihi/article/details/119600283