Flutter FFI 연구 노트 시리즈
- "Flutter FFI의 가장 간단한 예"
- "Flutter FFI 기본 데이터 유형"
- "플러터 FFI 기능"
- "플러터 FFI 문자열"
- "플러터 FFI 구조"
- "플러터 FFI 클래스"
- "플러터 FFI 어레이"
- "플러터 FFI 메모리 관리"
- 《플러터 FFI 다트 네이티브 API》
이전 장에서는 기본 데이터 유형, 문자열, 구조, 클래스 및 배열과 같은 지식 포인트를 소개했으며 다음으로 FFI의 메모리 관리를 소개합니다.
C 언어 개발 과정에서 메모리의 적용과 복구는 개발자 자신의 몫이며, 이전 글에서 메모리 할당과 복구에 대해 많이 설명하였으니 오늘 계속해서 알아보도록 하겠습니다.
1. 메모리 관리 소개
Dart FFI는 개발자가 Dart 코드를 사용하여 네이티브에서 메모리를 적용하고 해제할 수 있도록 하는 몇 가지 API를 제공합니다. 이러한 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
and는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. 자동 해제 풀 - 아레나
때로는 일부 작업을 수행하기 위해 일시적으로 메모리를 적용하고 작업이 완료된 후 메모리를 해제해야 하지만 종종 메모리 해제를 잊어버리거나 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()
function 은 메모리를 해제해야 하는 경우 호출해야 하는 무효 함수입니다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 및 기타 고급 사용법을 구현하는 방법을 소개합니다. 환영합니다.