引言:Dart FFI与跨语言交互的挑战
在现代软件开发中,跨语言交互已成为常态。无论是为了复用现有库、访问底层系统功能,还是为了追求极致性能,将不同语言编写的代码集成在一起的需求无处不在。Dart,作为一门为客户端优化而设计的语言,尤其在Flutter生态系统中大放异彩。然而,对于某些计算密集型任务、操作系统API调用或与特定硬件交互的场景,Dart的纯Dart实现可能无法满足要求。此时,Dart的外部函数接口(Foreign Function Interface,简称FFI)便应运而生,它提供了一种安全且高效的方式,让Dart代码能够直接调用C语言(以及C++中导出的C风格函数)库。
FFI的引入极大地扩展了Dart的能力边界,但同时也带来了跨语言编程固有的复杂性,尤其是内存管理方面的挑战。Dart拥有自动垃圾回收(GC)机制,开发者通常无需关心内存的分配与释放。而C/C++则采用手动内存管理,开发者必须精确地控制内存的生命周期。当Dart对象需要在C/C++代码中被引用时,如何安全地存储这些Dart对象的指针,并确保它们不会在Dart垃圾回收器不知情的情况下被回收,同时还要兼顾多线程环境下的内存可见性问题,这正是本讲座的核心议题——Dart FFI内存屏障与安全存储Dart对象指针。
我们将深入探讨Dart和C/C++内存管理模型的差异,剖析直接存储Dart指针的风险,并详细介绍Dart FFI提供的一系列句柄机制(Dart_Handle、Dart_PersistentHandle、Dart_WeakPersistentHandle)以及现代的Dart_FinalizerService和Dart_Finalizer。此外,我们还将从传统内存屏障的角度,讨论在跨语言、多线程场景下确保内存可见性和数据一致性的方法。通过丰富的代码示例和严谨的逻辑分析,我们将构建一个关于如何在C/C++内存中安全、高效、可靠地操作Dart对象指针的全面理解。
Dart内存管理概览:垃圾回收与对象生命周期
理解Dart FFI中内存安全的关键在于首先掌握Dart自身的内存管理机制。Dart是一门带有自动垃圾回收(Garbage Collection, GC)机制的语言。这意味着开发者通常不需要手动分配和释放内存;Dart运行时环境会自动跟踪对象的使用情况,并在对象不再被任何活动引用时回收其占用的内存。
1. Dart的堆和栈
- 栈(Stack):主要用于存储局部变量、函数参数和函数调用帧。栈内存由编译器自动管理,分配和释放速度快。当函数调用结束时,其对应的栈帧和局部变量会自动被弹出和回收。
- 堆(Heap):用于存储所有在程序运行时动态创建的对象实例。包括类实例、列表、映射、字符串等。堆内存的分配和释放由Dart的垃圾回收器负责。
2. 分代垃圾回收机制
Dart虚拟机(VM)采用了一种高效的分代垃圾回收策略,通常包括:
- 新生代(Young Generation):新创建的对象首先被分配到新生代。新生代GC(通常称为“Minor GC”或“Scavenge”)频繁运行,通过复制算法清理短生命周期的对象。存活下来的对象会被晋升到老生代。
- 老生代(Old Generation):经过多次新生代GC仍然存活的对象会被移动到老生代。老生代GC(通常称为“Major GC”或“Mark-Sweep”)运行频率较低,它采用标记-清除(Mark-Sweep)或标记-整理(Mark-Compact)算法来清理长期存活的对象。
这种分代回收策略基于“弱代假说”(Generational Hypothesis),即大多数对象生命周期很短,而少数对象生命周期很长。通过针对不同生命周期的对象采用不同的回收策略,可以显著提高GC效率。
3. 对象的创建、存活与回收
- 对象创建:当Dart代码通过
new关键字或字面量(如[]、{}、"")创建对象时,这些对象被分配到堆上。 - 对象存活:一个对象被认为是“存活的”(live),只要它能通过一系列引用链从GC根(GC Roots)访问到。GC根包括:
- 当前正在执行的函数栈上的局部变量。
- 静态字段。
- 被JIT编译器优化为寄存器中的值。
- FFI中通过
Dart_PersistentHandle等机制“告诉”GC的外部引用。
- 对象回收:当GC运行时,它会遍历所有GC根可达的对象。任何不可达的对象都被认为是“垃圾”,其占用的内存将被回收,并可供后续的对象分配使用。在对象被回收之前,如果注册了终结器(Finalizer),终结器回调会被执行,用于清理与该Dart对象关联的外部资源。
4. GC对外部引用的“无知”是问题的根源
Dart的GC机制是内省的,它只能感知到Dart堆内部的引用关系。这意味着如果一个Dart对象只是被C/C++代码中的一个裸指针引用,而没有任何Dart内部的引用指向它,那么对于Dart GC来说,这个对象就是不可达的,它将被视为垃圾并被回收。一旦对象被回收,C/C++代码中存储的裸指针就会变成一个“悬空指针”(dangling pointer),指向一块可能已被重新分配给其他用途的内存。当C/C++代码试图通过这个悬空指针访问原Dart对象时,将导致未定义行为,轻则数据损坏,重则程序崩溃。
这就是为什么在C/C++中安全存储Dart对象指针需要特殊处理的根本原因。我们需要一种机制来“告诉”Dart GC:“嘿,这个Dart对象虽然在Dart代码中可能看起来不可达了,但C/C++代码仍然在使用它,请暂时不要回收它!”反之,我们也需要一种机制来知道Dart对象何时被GC回收,以便C/C++代码可以安全地释放其持有的关联资源。
C/C++内存管理基础:手动控制与指针语义
与Dart的自动内存管理形成鲜明对比的是C/C++的手动内存管理模型。在C/C++中,开发者对内存的分配、使用和释放拥有极致的控制权。这种控制带来了性能上的优势和对系统资源的精确管理能力,但也伴随着更高的复杂性和潜在的错误风险。
1. C/C++的堆和栈
与Dart类似,C/C++程序也使用栈和堆来管理内存:
- 栈(Stack):与Dart栈的功能类似,用于存储局部变量、函数参数和返回地址。栈内存分配和释放非常快,由编译器自动处理。生命周期与函数调用绑定,函数返回后,栈上的局部变量即失效。
- 堆(Heap):也称为“自由存储区”,用于动态内存分配。程序在运行时可以通过
malloc/free(C语言)或new/delete(C++)来请求和释放任意大小的内存块。堆内存的生命周期由开发者手动控制,分配的内存会一直存在,直到被显式释放或程序结束。
2. malloc/free与new/delete
-
C语言(
malloc/free):malloc(size_t size):从堆中分配指定大小(字节)的内存块,并返回一个指向该内存块起始位置的void*指针。如果分配失败,返回NULL。free(void* ptr):释放先前由malloc、calloc或realloc分配的内存块。释放后,ptr变成一个悬空指针,不应再被使用。
#include <stdio.h> #include <stdlib.h> // For malloc and free void demo_c_memory_management() { int* my_int_array = (int*)malloc(5 * sizeof(int)); // Allocate space for 5 integers if (my_int_array == NULL) { fprintf(stderr, "Memory allocation failed!n"); return; } for (int i = 0; i < 5; ++i) { my_int_array[i] = i * 10; printf("my_int_array[%d] = %dn", i, my_int_array[i]); } free(my_int_array); // Free the allocated memory my_int_array = NULL; // Good practice: set pointer to NULL after freeing // Accessing my_int_array now would be undefined behavior (dangling pointer) } -
C++(
new/delete):new Type:为Type类型的单个对象分配内存,调用其构造函数,并返回指向该对象的指针。new Type[size]:为Type类型的size个对象分配内存,调用每个对象的构造函数,并返回指向数组第一个元素的指针。delete ptr:释放由new分配的单个对象的内存,并调用其析构函数。delete[] ptr:释放由new[]分配的数组的内存,并为数组中的每个对象调用析构函数。
#include <iostream> class MyClass { public: int value; MyClass(int v) : value(v) { std::cout << "MyClass(" << value << ") created.n"; } ~MyClass() { std::cout << "MyClass(" << value << ") destroyed.n"; } }; void demo_cpp_memory_management() { MyClass* obj = new MyClass(100); // Allocate a single object std::cout << "Object value: " << obj->value << "n"; delete obj; // Free the object MyClass* obj_array = new MyClass[3]{MyClass(1), MyClass(2), MyClass(3)}; // Allocate an array for (int i = 0; i < 3; ++i) { std::cout << "Array element " << i << " value: " << obj_array[i].value << "n"; } delete[] obj_array; // Free the array }
3. 裸指针的危险性:悬空指针、内存泄漏
手动内存管理带来了强大的控制力,但也引入了严重的安全隐患:
- 悬空指针(Dangling Pointers):当一块内存被释放后,但仍然有指针指向这块内存。此时,如果程序试图通过这个悬空指针访问内存,就会导致未定义行为。这块内存可能已经被操作系统回收,或者被重新分配给程序的其他部分。
int* ptr = (int*)malloc(sizeof(int)); *ptr = 10; free(ptr); // ptr is now a dangling pointer. // *ptr = 20; // DANGER! Undefined behavior. - 内存泄漏(Memory Leaks):当程序分配了内存,但在不再需要时未能释放它,导致这块内存无法被再次使用。长期运行的程序如果存在内存泄漏,最终会导致系统资源耗尽。
void memory_leak_example() { int* ptr = (int*)malloc(sizeof(int)); // ... use ptr ... // Forgetting to call free(ptr) here leads to a memory leak. } - 重复释放(Double Free):尝试释放同一块内存两次。这同样会导致未定义行为,可能损坏堆结构,甚至引发安全漏洞。
int* ptr = (int*)malloc(sizeof(int)); free(ptr); // free(ptr); // DANGER! Double free.
4. C/C++与Dart内存模型的根本差异
下表总结了Dart和C/C++内存管理模型的关键差异:
| 特性 | Dart | C/C++ |
|---|---|---|
| 内存管理方式 | 自动(垃圾回收) | 手动(malloc/free, new/delete) |
| 对象生命周期 | 由GC自动跟踪和管理,基于可达性。 | 由开发者显式控制,需要手动分配和释放。 |
| 指针安全性 | 默认安全,裸指针概念不直接暴露给开发者。 | 裸指针是核心概念,但易导致悬空指针、内存泄漏。 |
| 内存错误 | 较少出现内存泄漏和悬空指针错误。 | 常见内存泄漏、悬空指针、越界访问、重复释放等问题。 |
| 性能开销 | GC会引入停顿(通过分代GC和并发GC最小化)。 | 开发者需要承担内存管理的开销,但可预测性高。 |
| 系统控制 | 虚拟机抽象层,对底层内存控制有限。 | 直接操作内存地址,拥有对底层系统资源的完全控制。 |
正是这些根本性的差异,使得在C/C++中安全地存储Dart对象的指针成为一个复杂而关键的问题。Dart FFI必须提供一套机制来桥接这两种截然不同的内存管理范式,确保跨语言交互的内存安全。
问题剖析:为什么不能直接存储Dart对象指针
在理解了Dart和C/C++各自的内存管理机制后,我们现在可以清晰地阐明为什么不能简单地将Dart对象的原始内存地址(即裸指针)存储在C/C++内存中。这个问题的核心在于Dart垃圾回收器对C/C++引用的无知。
1. 直接存储Dart对象的原始指针的后果
假设我们有一个Dart对象,例如一个User类的实例:
class User {
String name;
int age;
User(this.name, this.age);
void greet() {
print("Hello, my name is $name and I am $age years old.");
}
}
在Dart FFI中,当我们将一个Dart对象传递给C函数时,Dart运行时会为这个对象创建一个临时的Dart_Handle。这个Dart_Handle实际上是一个不透明的句柄,它内部可能包含了对象的内存地址,但其具体实现是Dart VM的内部细节。我们不能直接解引用Dart_Handle来获取原始指针,更不应该尝试这样做。
然而,为了说明问题,我们假设存在某种方式可以获取到这个Dart对象的原始内存地址 raw_dart_object_ptr,并将其存储在一个C/C++的全局变量或堆分配的结构体中:
// 假设这是一个C/C++文件
void* global_dart_user_ptr = NULL; // 存储Dart User对象的裸指针 (示意,实际不可行)
// 某个FFI函数,从Dart接收User对象,并尝试存储其裸指针
// 这只是一个概念性错误示例,实际的Dart_Handle不能直接转换为裸指针
void store_dart_user_bad_example(void* dart_user_object_ptr) {
global_dart_user_ptr = dart_user_object_ptr;
printf("C: Stored Dart user object at address %pn", global_dart_user_ptr);
}
在Dart侧,我们可能这样调用:
import 'dart:ffi';
import 'dart:io';
// 假设我们已经设置好了FFI,并有了 store_dart_user_bad_example 的FFI绑定
// typedef StoreDartUserBadExampleNative = Void Function(Pointer<Void>);
// typedef StoreDartUserBadExampleDart = void Function(Pointer<Void>);
// late final StoreDartUserBadExampleDart storeDartUserBadExample;
// (实际FFI代码会更复杂,这里仅为示意)
// final dylib = Platform.isMacOS || Platform.isIOS
// ? DynamicLibrary.open('libmy_ffi_lib.dylib')
// : DynamicLibrary.open('libmy_ffi_lib.so');
// storeDartUserBadExample = dylib
// .lookupFunction<StoreDartUserBadExampleNative, StoreDartUserBadExampleDart>(
// 'store_dart_user_bad_example');
void main() {
User user = User("Alice", 30);
print("Dart: Created user: ${user.name}");
// !!! 严重错误示范:这里无法直接获取Dart对象的裸指针
// FFI函数通常接收Dart_Handle,然后C侧操作Dart_Handle
// 但是为了说明问题,我们假设这里传了一个概念上的“裸指针”
// storeDartUserBadExample(Pointer.fromAddress(user_raw_address)); // 假设可以获取
// 实际情况是,如果一个Dart对象在Dart代码中不再被引用,它就可能被回收
// 假设在调用 storeDartUserBadExample 之后,Dart代码中不再有对 user 的引用
// user = null; // 或者超出作用域
print("Dart: User object might become unreachable after this point.");
// 强制GC运行 (仅为测试目的,实际不应依赖)
// 这是高度不确定的,Dart VM的GC行为是复杂的
// 这里无法可靠地触发GC并立即回收对象
// 但是,如果长时间运行,并且没有其他Dart引用,GC最终会回收它
}
2. 垃圾回收器在不知情的情况下移动或回收对象
当Dart代码中不再有对user对象的引用时(例如,main函数执行完毕,或者user变量被设置为null),user对象就变成了垃圾回收的候选对象。
- 对象回收:当Dart GC运行时,它会遍历GC根可达的对象。由于
global_dart_user_ptr存在于C/C++内存中,Dart GC对其一无所知,它不会将其视为一个有效的引用。因此,user对象将被标记为不可达,并在下一次GC周期中被回收。其占用的内存将被释放,并可能被分配给新的Dart对象。 - 对象移动:更糟的是,即使对象没有被回收,分代垃圾回收器(特别是新生代GC或标记-整理GC)可能会为了优化内存布局而移动对象在内存中的位置。这意味着,即使C/C++中的裸指针指向的对象暂时没有被回收,但其内存地址也可能发生变化。
3. 导致悬空指针和程序崩溃的示例
无论对象是被回收还是被移动,C/C++代码中存储的global_dart_user_ptr都将变成一个悬空指针:
- 如果对象被回收,
global_dart_user_ptr指向的内存可能已经被重用,访问它将导致访问无效内存。 - 如果对象被移动,
global_dart_user_ptr仍然指向旧的内存地址,而对象已经位于新的地址。访问它将导致访问错误的数据。
在C/C++代码中,如果我们在Dart对象被回收或移动后,尝试通过global_dart_user_ptr来操作这个“Dart对象”(例如,试图读取其字段或调用其方法,通过FFI再次将此指针传回Dart),则会发生以下后果:
- 内存访问违规(Segmentation Fault / Access Violation):这是最常见且最直接的后果。程序试图访问一块它无权访问的内存区域,操作系统会终止程序。
- 数据损坏:如果悬空指针指向的内存恰好被重新分配给程序的其他部分(无论是Dart对象还是C/C++数据),那么对这块内存的写入或读取将破坏其他数据,导致难以调试的逻辑错误。
- 安全漏洞:在某些情况下,利用悬空指针可能导致信息泄露或代码执行。
实际的错误代码示例(仅为概念说明,无法直接运行)
// my_ffi_lib.c
#include <stdio.h>
#include <dart_api.h> // Dart FFI API
Dart_Handle global_dart_user_handle = NULL; // 用于存储Dart对象的句柄
// 假想的函数,演示错误做法(实际API不能直接将Dart_Handle转换为裸指针并存储)
// 如果我们能直接拿到Dart对象的裸指针并存储,那就会出现问题
// 这里的 Dart_Handle 是 Dart VM 内部的句柄,不是裸指针
// 这是一个错误示范,说明为什么不能绕过Dart API来“直接”存储
void store_dart_user_raw_ptr_bad(Dart_Handle dart_user_object) {
// 假设可以从 dart_user_object 中提取出裸指针 (这是错误的假设!)
// void* raw_ptr = (void*)dart_user_object; // 这种转换是错误的,Dart_Handle是不透明的
// global_dart_user_ptr = raw_ptr; // 如果真的能这样存储,就是悬空指针的根源
// 正确的做法是使用 Dart_PersistentHandle
// 但是为了演示问题,我们假设这里什么都没做,只是传递了对象
// 并且我们没有保存任何东西,只是说明一个概念上的危险
printf("C: Received Dart object handle %p. (Conceptual bad storage scenario)n", (void*)dart_user_object);
}
// 假想的函数,在Dart对象被回收后尝试访问 (假设我们之前错误地存储了裸指针)
// 这里的Dart_Handle应该是一个有效的句柄,但如果我们存储的是裸指针,它就是无效的
void access_dart_user_raw_ptr_bad() {
// 假设 global_dart_user_ptr (如果之前错误地存储了裸指针) 是有效的
// 这将导致崩溃或数据损坏
// Dart_Handle potentially_dangling_handle = (Dart_Handle)global_dart_user_ptr;
// Dart_Developer_Print("C: Attempting to access potentially dangling handle.");
// Dart_ToString(potentially_dangling_handle); // 这会崩溃
}
总结:
直接将Dart对象的原始内存地址存储在C/C++内存中是极其危险和错误的。Dart的垃圾回收器不会追踪C/C++中的裸指针引用,因此它可能会随时回收或移动对象。一旦这种情况发生,C/C++中的裸指针就会变成悬空指针,导致程序崩溃、数据损坏或更严重的安全问题。为了在C/C++中安全地引用Dart对象,我们必须使用Dart FFI提供的特定机制,这些机制能够“告诉”Dart GC关于C/C++引用的存在。
解决方案核心:Dart FFI的句柄机制
为了解决C/C++代码中安全引用Dart对象的问题,Dart FFI引入了一套句柄(Handle)机制。这些句柄是Dart VM提供的特殊类型,它们允许C/C++代码持有对Dart对象的引用,同时确保Dart GC能够感知到这些引用,从而避免对象被过早回收。
1. Dart_Handle:临时引用
Dart_Handle 是Dart FFI中最基础的句柄类型。它是一个不透明的指针类型(typedef void* Dart_Handle;),用于表示对Dart对象的引用。
-
特点:
Dart_Handle通常是临时的,仅在当前的FFI调用帧内有效。- 当你从Dart向C/C++传递一个Dart对象时,Dart VM会将其包装成一个
Dart_Handle。 - 在C/C++代码中,你可以通过
Dart_Handle来调用其他Dart API函数,例如获取对象的属性、调用对象的方法等。 - 一旦FFI调用返回到Dart,这个临时的
Dart_Handle就不再保证有效,其引用的对象可能会在随后的GC周期中被回收。 Dart_Handle不增加Dart对象的引用计数,它本身不阻止垃圾回收。
-
使用场景:
- 在C/C++函数内部,接收从Dart传递过来的对象,并立即对其进行操作。
- 创建新的Dart对象并将其返回给Dart(此时返回的也是一个
Dart_Handle)。
-
限制:
- 不能将
Dart_Handle存储在C/C++的全局变量或堆分配的结构体中,因为它的生命周期是短暂的。如果尝试这样做,等到下一次FFI调用时,它很可能已经失效。
- 不能将
代码示例:Dart_Handle 的简单使用
Dart 代码 (lib/main.dart)
import 'dart:ffi';
import 'dart:io';
import 'package:path/path.dart' as path;
// 定义C函数签名
typedef PrintDartObjectInfoNative = Void Function(Handle dartObject);
typedef PrintDartObjectInfoDart = void Function(Object dartObject);
// 定义一个简单的Dart类
class MyDartClass {
String message;
int id;
MyDartClass(this.message, this.id);
void sayHello() {
print("MyDartClass says: ${message}, ID: ${id}");
}
}
void main() {
final dylibPath = path.join(Directory.current.path, 'my_ffi_lib');
final dylib = DynamicLibrary.open(dylibPath);
final printDartObjectInfo = dylib.lookupFunction<
PrintDartObjectInfoNative,
PrintDartObjectInfoDart>('print_dart_object_info');
MyDartClass myObject = MyDartClass("Hello from Dart", 123);
print("Dart: Calling C function with MyDartClass instance.");
printDartObjectInfo(myObject); // 将Dart对象传递给C
print("Dart: C function call finished.");
// myObject 仍然在Dart中有引用,所以不会被GC回收
// 如果这里 myObject = null; 并且没有其他引用,它可能在未来被回收
}
C/C++ 代码 (my_ffi_lib.c)
#include <stdio.h>
#include <string.h>
#include <dart_api.h> // Dart FFI API
// 导出C函数,接收一个Dart_Handle
DART_EXPORT void print_dart_object_info(Dart_Handle dart_object_handle) {
// 检查句柄是否有效
if (Dart_IsNull(dart_object_handle)) {
fprintf(stderr, "C: Received a null Dart object handle.n");
return;
}
// 获取Dart对象的字符串表示
Dart_Handle string_handle = Dart_ToString(dart_object_handle);
if (Dart_IsError(string_handle)) {
fprintf(stderr, "C: Error converting Dart object to string: %sn", Dart_GetError(string_handle));
return;
}
const char* object_str;
Dart_StringToCString(string_handle, &object_str);
printf("C: Received Dart object: %sn", object_str);
// 尝试获取对象的某个属性 (例如 MyDartClass.message)
Dart_Handle message_field_name = Dart_NewStringFromCString("message");
Dart_Handle message_value_handle = Dart_GetField(dart_object_handle, message_field_name);
if (!Dart_IsError(message_value_handle)) {
const char* message_str;
Dart_StringToCString(message_value_handle, &message_str);
printf("C: MyDartClass.message = %sn", message_str);
} else {
fprintf(stderr, "C: Error getting 'message' field: %sn", Dart_GetError(message_value_handle));
}
// 在函数返回后,dart_object_handle 就不再保证有效了。
// 不能将其存储起来以供后续使用。
}
2. Dart_PersistentHandle:强引用,阻止GC回收对象
Dart_PersistentHandle 是解决C/C++长期持有Dart对象引用的主要机制。它是一个“强引用”,意味着只要Dart_PersistentHandle存在,它所引用的Dart对象就不会被垃圾回收器回收。
-
特点:
- 阻止GC:它会增加Dart对象的引用计数或以其他方式通知GC该对象是活跃的,从而阻止GC回收该对象。
- 长期有效:可以在FFI调用之间持久化,存储在C/C++的全局变量、堆分配的结构体或类成员中。
- 需要手动管理:与C/C++的手动内存管理类似,
Dart_PersistentHandle也需要手动创建和销毁。
-
创建、使用与释放:
- 创建:使用
Dart_NewPersistentHandle(Dart_Handle object)函数来创建一个持久句柄。它接受一个临时的Dart_Handle作为参数,并返回一个新的Dart_PersistentHandle。 - 使用:
Dart_PersistentHandle可以像普通的Dart_Handle一样使用,作为参数传递给其他Dart API函数。 - 释放:使用
Dart_DeletePersistentHandle(Dart_PersistentHandle handle)函数来释放持久句柄。一旦不再需要C/C++代码中的Dart对象引用,就必须调用此函数。
- 创建:使用
-
生命周期管理:谁负责释放?
- 管理
Dart_PersistentHandle的生命周期是C/C++代码的责任。 - 如果忘记释放,即使Dart代码中不再有对该对象的引用,该对象也不会被GC回收,从而导致内存泄漏。这是一种跨语言的内存泄漏。
- 管理
代码示例:Dart_PersistentHandle 的使用
Dart 代码 (lib/main.dart)
import 'dart:ffi';
import 'dart:io';
import 'package:path/path.dart' as path;
// 定义C函数签名
typedef StoreDartObjectNative = Void Function(Handle dartObject);
typedef StoreDartObjectDart = void Function(Object dartObject);
typedef AccessStoredObjectNative = Void Function();
typedef AccessStoredObjectDart = void Function();
typedef FreeStoredObjectNative = Void Function();
typedef FreeStoredObjectDart = void Function();
class MyDartClass {
String message;
int id;
MyDartClass(this.message, this.id);
void sayHello() {
print("MyDartClass says: ${message}, ID: ${id}");
}
@override
String toString() => "MyDartClass(message: '$message', id: $id)";
}
void main() {
final dylibPath = path.join(Directory.current.path, 'my_ffi_lib');
final dylib = DynamicLibrary.open(dylibPath);
final storeDartObject = dylib.lookupFunction<
StoreDartObjectNative,
StoreDartObjectDart>('store_dart_object');
final accessStoredObject = dylib.lookupFunction<
AccessStoredObjectNative,
AccessStoredObjectDart>('access_stored_object');
final freeStoredObject = dylib.lookupFunction<
FreeStoredObjectNative,
FreeStoredObjectDart>('free_stored_object');
MyDartClass myObject = MyDartClass("Persistent Hello", 456);
print("Dart: Created myObject: $myObject");
print("Dart: Storing myObject in C memory using Dart_PersistentHandle.");
storeDartObject(myObject); // 存储对象
// 此时 Dart 代码中可以清除对 myObject 的引用,但 C 侧的持久句柄会阻止 GC
myObject = MyDartClass("Another object", 789); // 替换 myObject
print("Dart: Replaced myObject. Original object should still be alive due to C handle.");
print("Dart: Accessing stored object from C.");
accessStoredObject(); // 访问存储的对象
print("Dart: Freeing stored object in C.");
freeStoredObject(); // 释放句柄
// 此时,原始的 "Persistent Hello" 对象不再被任何地方引用,最终会被GC回收
print("Dart: Original object is now eligible for GC.");
}
C/C++ 代码 (my_ffi_lib.c)
#include <stdio.h>
#include <string.h>
#include <dart_api.h> // Dart FFI API
// 全局变量,用于存储持久句柄
static Dart_PersistentHandle stored_persistent_handle = NULL;
DART_EXPORT void store_dart_object(Dart_Handle dart_object_handle) {
if (Dart_IsNull(dart_object_handle)) {
fprintf(stderr, "C: Received a null Dart object handle for storage.n");
return;
}
// 检查是否已经存储了一个对象,避免内存泄漏
if (stored_persistent_handle != NULL) {
fprintf(stderr, "C: Warning: Overwriting previously stored object without freeing. Releasing old handle.n");
Dart_DeletePersistentHandle(stored_persistent_handle);
}
// 创建持久句柄
stored_persistent_handle = Dart_NewPersistentHandle(dart_object_handle);
if (Dart_IsError(stored_persistent_handle)) {
fprintf(stderr, "C: Error creating persistent handle: %sn", Dart_GetError(stored_persistent_handle));
stored_persistent_handle = NULL; // 确保句柄为NULL
return;
}
printf("C: Stored Dart object with persistent handle %p.n", (void*)stored_persistent_handle);
}
DART_EXPORT void access_stored_object() {
if (stored_persistent_handle == NULL) {
printf("C: No Dart object stored.n");
return;
}
// 将持久句柄转换为临时句柄,以便使用Dart API
Dart_Handle temp_handle = Dart_HandleFromPersistent(stored_persistent_handle);
if (Dart_IsError(temp_handle)) {
fprintf(stderr, "C: Error converting persistent handle to temporary: %sn", Dart_GetError(temp_handle));
return;
}
Dart_Handle string_handle = Dart_ToString(temp_handle);
if (!Dart_IsError(string_handle)) {
const char* object_str;
Dart_StringToCString(string_handle, &object_str);
printf("C: Accessed stored Dart object: %sn", object_str);
} else {
fprintf(stderr, "C: Error getting string representation of stored object: %sn", Dart_GetError(string_handle));
}
}
DART_EXPORT void free_stored_object() {
if (stored_persistent_handle != NULL) {
// 释放持久句柄
Dart_DeletePersistentHandle(stored_persistent_handle);
stored_persistent_handle = NULL; // 设为NULL,防止重复释放
printf("C: Freed stored Dart object persistent handle.n");
} else {
printf("C: No Dart object handle to free.n");
}
}
3. Dart_WeakPersistentHandle:弱引用,不阻止GC回收对象
Dart_WeakPersistentHandle 是一种“弱引用”机制。它允许C/C++代码持有对Dart对象的引用,但这种引用不会阻止Dart垃圾回收器回收对象。当对象被GC回收时,Dart_WeakPersistentHandle 会自动失效。
-
特点:
- 不阻止GC:这是与
Dart_PersistentHandle最主要的区别。弱句柄不会增加Dart对象的引用计数,对象仍可能被GC回收。 - 失效通知:当它所引用的Dart对象被GC回收时,
Dart_WeakPersistentHandle会自动变为“空”或“无效”状态。C/C++代码可以通过Dart_HandleFromWeakPersistent检查其有效性。 - 需要手动创建和销毁:与强句柄类似,弱句柄也需要手动创建和销毁。
- 不阻止GC:这是与
-
创建、使用与处理对象被回收:
- 创建:使用
Dart_NewWeakPersistentHandle(Dart_Handle object, void* peer, Dart_WeakPersistentHandleFinalizer finalizer)。object:要弱引用的Dart对象。peer:一个与Dart对象关联的C/C++指针。当Dart对象被回收时,这个peer指针会传递给终结器。这允许C/C++代码在对象被回收时清理其关联的C/C++资源。finalizer:一个回调函数(Dart_WeakPersistentHandleFinalizer),当object被GC回收时,VM会调用这个函数。
- 检查有效性:在每次使用弱句柄之前,应通过
Dart_HandleFromWeakPersistent(Dart_WeakPersistentHandle handle)获取一个临时Dart_Handle。如果返回的是空句柄(Dart_Null()),则表示原Dart对象已被回收。 - 释放:使用
Dart_DeleteWeakPersistentHandle(Dart_WeakPersistentHandle handle)释放弱句柄。如果句柄引用的对象已被回收,此操作仍是安全的。
- 创建:使用
-
何时使用弱句柄?
- 当C/C++代码需要观察Dart对象的生命周期,并在对象被回收时执行清理操作(例如,释放与Dart对象关联的C/C++资源)。
- 当C/C++代码持有的引用不应该阻止Dart对象被GC回收时。
- 作为缓存机制,C/C++可以缓存Dart对象的弱引用,如果对象仍然存活则使用,否则重新创建或获取。
代码示例:Dart_WeakPersistentHandle 的使用
Dart 代码 (lib/main.dart)
import 'dart:ffi';
import 'dart:io';
import 'package:path/path.dart' as path;
// 定义C函数签名
typedef StoreWeakDartObjectNative = Void Function(Handle dartObject);
typedef StoreWeakDartObjectDart = void Function(Object dartObject);
typedef AccessWeakStoredObjectNative = Void Function();
typedef AccessWeakStoredObjectDart = void Function();
typedef FreeWeakStoredObjectNative = Void Function();
typedef FreeWeakStoredObjectDart = void Function();
// 与之前相同的Dart类
class MyDartClass {
String message;
int id;
MyDartClass(this.message, this.id);
@override
String toString() => "MyDartClass(message: '$message', id: $id)";
// 添加一个析构模拟,观察何时被GC回收
// 实际上Dart没有析构函数,这里只是一个概念性说明
// 真正的清理会通过Finalizer或WeakPersistentHandleFinalizer
void dispose() {
print("Dart: MyDartClass($id) is being disposed (conceptually).");
}
}
void main() async {
final dylibPath = path.join(Directory.current.path, 'my_ffi_lib');
final dylib = DynamicLibrary.open(dylibPath);
final storeWeakDartObject = dylib.lookupFunction<
StoreWeakDartObjectNative,
StoreWeakDartObjectDart>('store_weak_dart_object');
final accessWeakStoredObject = dylib.lookupFunction<
AccessWeakStoredObjectNative,
AccessWeakStoredObjectDart>('access_weak_stored_object');
final freeWeakStoredObject = dylib.lookupFunction<
FreeWeakStoredObjectNative,
FreeWeakStoredObjectDart>('free_weak_stored_object');
MyDartClass myObject = MyDartClass("Weak Hello", 789);
print("Dart: Created myObject: $myObject");
print("Dart: Storing myObject in C memory using Dart_WeakPersistentHandle.");
storeWeakDartObject(myObject); // 存储对象
print("Dart: Accessing weakly stored object from C (should be alive).");
accessWeakStoredObject();
// 清除Dart代码对myObject的引用,使其成为GC候选者
myObject = MyDartClass("Another object", 999);
print("Dart: Cleared Dart's reference to original object. It's now GC eligible.");
// 等待一段时间,希望GC运行并回收对象
// 实际GC行为是不可预测的,可能需要多次尝试或更长时间
for (int i = 0; i < 5; i++) {
print("Dart: Waiting for GC... ($i/5)");
await Future.delayed(Duration(seconds: 1));
// 每次迭代都尝试访问,看是否被回收
accessWeakStoredObject();
// 强制GC提示 (仅供调试,不保证立即回收)
// gc(); // Dart_WeakPersistentHandleFinalizer 会在对象被GC时触发
}
print("Dart: Attempting to access weakly stored object again.");
accessWeakStoredObject(); // 此时可能已经被回收
print("Dart: Freeing weak handle in C.");
freeWeakStoredObject(); // 释放句柄
}
C/C++ 代码 (my_ffi_lib.c)
#include <stdio.h>
#include <string.h>
#include <dart_api.h> // Dart FFI API
#include <stdlib.h> // For malloc/free
// 定义一个C结构体作为peer数据,用于演示清理
typedef struct {
int id;
char* description;
} C_Resource;
// 全局变量,用于存储弱持久句柄和关联的C资源
static Dart_WeakPersistentHandle stored_weak_handle = NULL;
static C_Resource* stored_c_resource = NULL;
// 弱持久句柄的终结器回调函数
void weak_handle_finalizer(void* isolate_callback_data, Dart_WeakPersistentHandle handle, void* peer) {
(void)isolate_callback_data; // 未使用
(void)handle; // 未使用,因为我们知道是哪个句柄触发的
C_Resource* resource = (C_Resource*)peer;
if (resource != NULL) {
printf("C: WeakPersistentHandleFinalizer triggered! Dart object (ID: %d) was GC'd. Cleaning up C resource: %s.n",
resource->id, resource->description);
free(resource->description); // 释放C字符串
free(resource); // 释放C_Resource结构体
// 注意:这里只是清理了peer数据,weak_handle本身需要在C代码中显式删除
// 但在这个回调中,我们知道Dart对象已经被回收了,所以我们也可以将其标记为无效
} else {
printf("C: WeakPersistentHandleFinalizer triggered, but no C resource peer was provided.n");
}
// 重要的是,在终结器触发后,associated_weak_handle 仍然存在,但其引用的Dart对象已无效
// 我们可以在此设置 stored_weak_handle = NULL; 或者在外部 free_weak_stored_object 中处理
// 为了简化,我们假设 free_weak_stored_object 会在Dart侧调用
}
DART_EXPORT void store_weak_dart_object(Dart_Handle dart_object_handle) {
if (Dart_IsNull(dart_object_handle)) {
fprintf(stderr, "C: Received a null Dart object handle for weak storage.n");
return;
}
// 清理旧的弱句柄和资源,避免泄漏
if (stored_weak_handle != NULL) {
fprintf(stderr, "C: Warning: Overwriting previously stored weak object without freeing. Releasing old handle.n");
Dart_DeleteWeakPersistentHandle(stored_weak_handle);
stored_weak_handle = NULL; // 确保置空
}
if (stored_c_resource != NULL) {
printf("C: Warning: Old C resource not freed before new storage. Freeing now.n");
free(stored_c_resource->description);
free(stored_c_resource);
stored_c_resource = NULL;
}
// 创建一个模拟的C资源,与Dart对象关联
stored_c_resource = (C_Resource*)malloc(sizeof(C_Resource));
if (stored_c_resource == NULL) {
fprintf(stderr, "C: Failed to allocate C_Resource.n");
return;
}
stored_c_resource->id = 789; // 假设从Dart对象中获取ID,这里简化
const char* desc_str = "Associated C data for WeakPersistentHandle";
stored_c_resource->description = (char*)malloc(strlen(desc_str) + 1);
if (stored_c_resource->description == NULL) {
fprintf(stderr, "C: Failed to allocate description string.n");
free(stored_c_resource);
stored_c_resource = NULL;
return;
}
strcpy(stored_c_resource->description, desc_str);
// 创建弱持久句柄,并传入C资源作为peer
stored_weak_handle = Dart_NewWeakPersistentHandle(dart_object_handle, stored_c_resource, weak_handle_finalizer);
if (Dart_IsError(stored_weak_handle)) {
fprintf(stderr, "C: Error creating weak persistent handle: %sn", Dart_GetError(stored_weak_handle));
// 如果创建失败,释放C资源
free(stored_c_resource->description);
free(stored_c_resource);
stored_c_resource = NULL;
stored_weak_handle = NULL;
return;
}
printf("C: Stored Dart object with weak persistent handle %p, associated C resource ID: %d.n",
(void*)stored_weak_handle, stored_c_resource->id);
}
DART_EXPORT void access_weak_stored_object() {
if (stored_weak_handle == NULL) {
printf("C: No Dart object weak handle stored.n");
return;
}
// 尝试将弱句柄转换为临时句柄
Dart_Handle temp_handle = Dart_HandleFromWeakPersistent(stored_weak_handle);
if (Dart_IsNull(temp_handle)) {
printf("C: Weakly stored Dart object has been garbage collected.n");
// 由于对象已被回收,我们可以安全地删除弱句柄并清理C资源
Dart_DeleteWeakPersistentHandle(stored_weak_handle);
stored_weak_handle = NULL;
if (stored_c_resource != NULL) {
printf("C: Cleaning up C resource associated with collected weak object.n");
free(stored_c_resource->description);
free(stored_c_resource);
stored_c_resource = NULL;
}
} else {
Dart_Handle string_handle = Dart_ToString(temp_handle);
if (!Dart_IsError(string_handle)) {
const char* object_str;
Dart_StringToCString(string_handle, &object_str);
printf("C: Accessed weakly stored Dart object (still alive): %sn", object_str);
} else {
fprintf(stderr, "C: Error getting string representation of weakly stored object: %sn", Dart_GetError(string_handle));
}
}
}
DART_EXPORT void free_weak_stored_object() {
if (stored_weak_handle != NULL) {
// 释放弱持久句柄
Dart_DeleteWeakPersistentHandle(stored_weak_handle);
stored_weak_handle = NULL;
printf("C: Freed stored Dart object weak persistent handle.n");
} else {
printf("C: No Dart object weak handle to free.n");
}
// 清理C资源,如果它还没有被终结器清理
if (stored_c_resource != NULL) {
printf("C: Cleaning up remaining C resource.n");
free(stored_c_resource->description);
free(stored_c_resource);
stored_c_resource = NULL;
}
}
C/C++句柄类型对比表
| 特性 | Dart_Handle |
Dart_PersistentHandle |
Dart_WeakPersistentHandle |
|---|---|---|---|
| 引用强度 | 临时引用,不阻止GC | 强引用,阻止GC回收对象 | 弱引用,不阻止GC回收对象 |
| 生命周期 | 仅在当前FFI调用帧内有效 | 手动管理,长期有效,跨FFI调用 | 手动管理,长期有效,跨FFI调用,对象GC后自动失效 |
| 存储位置 | 不应存储,仅在函数参数或返回值中使用 | 可存储在C/C++全局变量、堆结构体、类成员中 | 可存储在C/C++全局变量、堆结构体、类成员中 |
| GC影响 | 不影响GC | 阻止GC回收所引用对象,可能导致内存泄漏 | 不阻止GC回收所引用对象 |
| 释放机制 | 无需显式释放(VM自动处理) | 必须手动调用 Dart_DeletePersistentHandle |
必须手动调用 Dart_DeleteWeakPersistentHandle |
| 对象回收通知 | 无 | 无 | 通过 Dart_WeakPersistentHandleFinalizer 回调通知 |
| 主要用途 | 短暂操作Dart对象 | 确保Dart对象在C/C++代码使用期间保持活跃 | 观察Dart对象生命周期,清理关联C/C++资源,实现缓存 |
现代解决方案:Dart_FinalizerService与Dart_Finalizer
Dart_WeakPersistentHandle 提供了一种在Dart对象被回收时执行C/C++清理的机制。然而,随着Dart FFI的不断发展,Dart 2.17版本引入了更强大、更灵活且更易于使用的 Dart_FinalizerService 和 Dart_Finalizer API。它们旨在提供一个更健壮的终结器(Finalizer)机制,用于管理与Dart对象生命周期绑定的外部(C/C++)资源。
1. 引入背景:WeakPersistentHandle 的局限性
Dart_WeakPersistentHandle 的终结器回调 (Dart_WeakPersistentHandleFinalizer) 有一些限制:
- 一次性:每个
WeakPersistentHandle只能关联一个终结器回调。 - 句柄管理:需要手动创建和删除弱句柄,并在终结器中处理
peer数据的清理。 - 不透明性:
peer数据只是一个void*,类型安全较差。 - 并发问题:终结器在GC线程中运行,需要注意线程安全问题。
Dart_FinalizerService 和 Dart_Finalizer 旨在解决这些问题,提供一个更结构化、更安全的终结器框架。
2. Dart_FinalizerService:管理终结器
Dart_FinalizerService 是一个抽象的“服务”,负责管理一系列的 Dart_Finalizer。它允许开发者注册一个全局的终结器服务,然后将多个 Dart_Finalizer 实例关联到这个服务。
-
创建:
Dart_NewFinalizerService(Dart_NativeMessageHandler callback, int message_port_id, int max_size)。callback:当终结器触发时,VM会调用此回调函数。这个回调是在一个隔离(isolate)的线程中执行的,通常是一个与FFI交互的辅助隔离。message_port_id:一个Dart端口ID,用于将终结器信息发送回Dart(如果需要)。通常在C/C++中处理终结器,所以这里可以为0。max_size:服务可以处理的最大终结器数量。
-
删除:
Dart_DeleteFinalizerService(Dart_FinalizerService service)。当不再需要这个服务时,需要显式删除。
3. Dart_Finalizer:将C/C++资源与Dart对象生命周期绑定
Dart_Finalizer 是核心组件,它将一个Dart对象与一个C/C++资源(即 peer 数据)以及一个清理回调函数绑定。当Dart对象被垃圾回收时,其关联的 Dart_Finalizer 会被触发,执行清理回调。
-
工作原理:
- 你创建一个
Dart_Finalizer实例。 - 你将它与一个Dart对象和一个
peer数据(通常是一个指向C/C++资源的指针)关联起来。 - 你提供一个清理回调函数(
Dart_FinalizerService的callback)。 - 当关联的Dart对象在GC中被发现是不可达时,VM会触发
Dart_FinalizerService的callback。 callback会接收到peer数据,从而可以清理C/C++资源。
- 你创建一个
-
创建:
Dart_NewFinalizer(Dart_FinalizerService service, Dart_Handle object, void* peer, int external_allocation_size, Dart_FinalizerType type)。service:你之前创建的Dart_FinalizerService实例。object:要跟踪的Dart对象。peer:一个指向C/C++资源的指针,当object被回收时,这个peer会被传递给终结器回调。external_allocation_size:可选参数,用于告知Dart GC这个peer数据占用了多少外部内存。VM会将其计入Dart的内存使用统计中,有助于GC更早地运行。如果为0,则表示不追踪。type:终结器类型,通常为Dart_FinalizerType_Normal。
-
删除:
Dart_DeleteFinalizer(Dart_Finalizer finalizer)。如果在Dart对象被GC之前,C/C++代码决定不再需要终结器,可以提前删除。 -
应用场景:
- C/C++资源(文件句柄、网络连接、GPU资源等)的自动清理:这是最典型的应用。例如,一个Dart
File对象在C/C++层可能对应一个文件描述符。当DartFile对象被回收时,终结器可以自动关闭文件描述符。 - 观察Dart对象回收:在Dart对象被回收时执行任何必要的C/C++清理逻辑。
- 管理Dart对象缓存:虽然
WeakPersistentHandle也可以,但Finalizer提供了更结构化的清理。
- C/C++资源(文件句柄、网络连接、GPU资源等)的自动清理:这是最典型的应用。例如,一个Dart
4. 代码示例:使用Dart_Finalizer清理C资源并观察Dart对象回收
这个例子将演示如何创建一个Dart_FinalizerService,然后使用Dart_Finalizer将一个Dart对象与一个C资源绑定。当Dart对象被回收时,C资源会被自动清理。
C/C++ 代码 (my_ffi_lib.c)
#include <stdio.h>
#include <stdlib.h> // For malloc/free
#include <string.h> // For strcpy
#include <dart_api.h> // Dart FFI API
// 定义一个C结构体作为peer数据,代表一个外部资源
typedef struct {
int resource_id;
char* resource_name;
// ... 其他C资源句柄,例如文件描述符、网络套接字等
} ExternalCResource;
// 全局的FinalizerService和Finalizer实例
static Dart_FinalizerService my_finalizer_service = NULL;
static Dart_Finalizer my_finalizer = NULL; // 存储一个Finalizer实例
static ExternalCResource* current_c_resource = NULL; // 跟踪当前关联的C资源
// 终结器回调函数。当Dart对象被GC时,VM会调用此函数。
// 注意:这个回调在一个特殊的FinalizerService隔离中运行,不是主Dart隔离。
void finalizer_callback(void* isolate_callback_data, Dart_Finalizer finalizer, void* peer) {
(void)isolate_callback_data; // 未使用
(void)finalizer; // 未使用,因为我们知道是哪个FinalizerService的回调
ExternalCResource* resource = (ExternalCResource*)peer;
if (resource != NULL) {
printf("C Finalizer: Dart object (ID: %d) was GC'd. Cleaning up C resource: '%s'.n",
resource->resource_id, resource->resource_name);
free(resource->resource_name); // 释放C字符串内存
free(resource); // 释放ExternalCResource结构体内存
// 关键:在这里将全局跟踪的C资源指针置空,防止后续C代码误用
if (resource == current_c_resource) {
current_c_resource = NULL;
}
} else {
printf("C Finalizer: Dart object was GC'd, but no C resource peer was provided.n");
}
// 注意:Finalizer本身不需要在这里删除,它会随着服务一起被管理
// 但是如果一个FinalizerService只负责一个Finalizer,并且你希望清理FinalizerService
// 那么在所有Finalizer都触发后,你可以考虑删除FinalizerService。
}
// FFI函数:初始化FinalizerService
DART_EXPORT void init_finalizer_service() {
if (my_finalizer_service == NULL) {
// 创建FinalizerService。第二个参数是消息端口ID,这里设置为0表示不向Dart发送消息。
// 第三个参数是最大大小,这里设置为1000作为示例。
my_finalizer_service = Dart_NewFinalizerService(finalizer_callback, 0, 1000);
if (Dart_IsError(my_finalizer_service)) {
fprintf(stderr, "C: Error creating FinalizerService: %sn", Dart_GetError(my_finalizer_service));
my_finalizer_service = NULL;
return;
}
printf("C: FinalizerService initialized: %pn", (void*)my_finalizer_service);
} else {
printf("C: FinalizerService already initialized.n");
}
}
// FFI函数:注册Dart对象和C资源到Finalizer
DART_EXPORT void register_finalizer(Dart_Handle dart_object_handle, int resource_id, const char* resource_name_cstr) {
if (my_finalizer_service == NULL) {
fprintf(stderr, "C: FinalizerService not initialized. Cannot register finalizer.n");
return;
}
if (Dart_IsNull(dart_object_handle)) {
fprintf(stderr, "C: Received null Dart object handle for finalizer registration.n");
return;
}
// 清理旧的Finalizer和资源,避免泄漏
if (my_finalizer != NULL) {
fprintf(stderr, "C: Warning: Overwriting previously registered Finalizer. Deleting old one.n");
Dart_DeleteFinalizer(my_finalizer); // 删除旧的Finalizer
my_finalizer = NULL;
}
if (current_c_resource != NULL) {
printf("C: Warning: Old C resource not freed before new registration. Freeing now.n");
free(current_c_resource->resource_name);
free(current_c_resource);
current_c_resource = NULL;
}
// 1. 创建C资源
ExternalCResource* new_c_resource = (ExternalCResource*)malloc(sizeof(ExternalCResource));
if (new_c_resource == NULL) {
fprintf(stderr, "C: Failed to allocate ExternalCResource.n");
return;
}
new_c_resource->resource_id = resource_id;
new_c_resource->resource_name = (char*)malloc(strlen(resource_name_cstr) + 1);
if (new_c_resource->resource_name == NULL) {
fprintf(stderr, "C: Failed to allocate resource_name string.n");
free(new_c_resource);
return;
}
strcpy(new_c_resource->resource_name, resource_name_cstr);
current_c_resource = new_c_resource; // 跟踪当前资源
// 2. 注册Finalizer
// external_allocation_size 告诉GC这个peer数据占用了多少外部内存,有助于GC调度
my_finalizer = Dart_NewFinalizer(my_finalizer_service, dart_object_handle, new_c_resource, sizeof(ExternalCResource) + strlen(resource_name_cstr) + 1, Dart_FinalizerType_Normal);
if (Dart_IsError(my_finalizer)) {
fprintf(stderr, "C: Error creating Finalizer: %sn", Dart_GetError(my_finalizer));
// 如果注册失败,释放C资源
free(new_c_resource->resource_name);
free(new_c_resource);
current_c_resource = NULL;
my_finalizer = NULL;
return;
}
printf("C: Registered Finalizer %p for Dart object, tracking C resource ID: %d ('%s').n",
(void*)my_finalizer, new_c_resource->resource_id, new_c_resource->resource_name);
}
// FFI函数:获取当前C资源的信息 (仅为调试目的,实际不应在FinalizerService管理的对象上进行强引用)
// 注意:这个函数不应该被设计成能够获取到已经被Finalizer管理的C资源,
// 因为Finalizer管理的是Dart对象的生命周期,而不是C资源的生命周期。
// 如果C资源仍需要被C代码访问,那么它就不应该完全依赖Finalizer。
// 这里仅仅是展示,如果Finalizer尚未触发,资源可能还在。
DART_EXPORT void check_c_resource_status() {
if (current_c_resource != NULL) {
printf("C: Current C resource (ID: %d, Name: '%s') is still active.n",
current_c_resource->resource_id, current_c_resource->resource_name);
} else {
printf("C: No active C resource is being tracked (either not registered or already cleaned by Finalizer).n");
}
}
// FFI函数:清理FinalizerService
DART_EXPORT void shutdown_finalizer_service() {
if (my_finalizer != NULL) {
Dart_DeleteFinalizer(my_finalizer);
my_finalizer = NULL;
printf("C: Deleted pending Finalizer.n");
}
if (my_finalizer_service != NULL) {
// 在关闭服务前,确保所有关联的Finalizer已被处理或删除
Dart_DeleteFinalizerService(my_finalizer_service);
my_finalizer_service = NULL;
printf("C: FinalizerService shut down.n");
} else {
printf("C: FinalizerService not active.n");
}
// 确保任何未被Finalizer清理的C资源也被清理
if (current_c_resource != NULL) {
printf("C: Cleaning up remaining C resource during shutdown (ID: %d, Name: '%s').n",
current_c_resource->resource_id, current_c_resource->resource_name);
free(current_c_resource->resource_name);
free(current_c_resource);
current_c_resource = NULL;
}
}
Dart 代码 (lib/main.dart)
import 'dart:ffi';
import 'dart:io';
import 'package:path/path.dart' as path;
// 定义C函数签名
typedef InitFinalizerServiceNative = Void Function();
typedef InitFinalizerServiceDart = void Function();
typedef RegisterFinalizerNative = Void Function(Handle dartObject, Int32 resourceId, Pointer<Utf8> resourceName);
typedef RegisterFinalizerDart = void Function(Object dartObject, int resourceId, Pointer<Utf8> resourceName);
typedef CheckCResourceStatusNative = Void Function();
typedef CheckCResourceStatusDart = void Function();
typedef ShutdownFinalizerServiceNative = Void Function();
typedef ShutdownFinalizerServiceDart = void Function();
// 与之前相同的Dart类
class MyDartResourceHolder {
String name;
int id;
MyDartResourceHolder(this.name, this.id);
@override
String toString() => "MyDartResourceHolder(name: '$name', id: $id)";
}
void main() async {
final dylibPath = path.join(Directory.current.path, 'my_ffi_lib');
final dylib = DynamicLibrary.open(dylibPath);
final initFinalizerService = dylib.lookupFunction<
InitFinalizerServiceNative,
InitFinalizerServiceDart>('init_finalizer_service');
final registerFinalizer = dylib.lookupFunction<
RegisterFinalizerNative,
RegisterFinalizerDart>('register_finalizer');
final checkCResourceStatus = dylib.lookupFunction<
CheckCResourceStatusNative,
CheckCResourceStatusDart>('check_c_resource_status');
final shutdownFinalizerService = dylib.lookupFunction<
ShutdownFinalizerServiceNative,
ShutdownFinalizerServiceDart>('shutdown_finalizer_service');
initFinalizerService(); // 初始化Finalizer服务
MyDartResourceHolder resourceHolder = MyDartResourceHolder("Database Connection", 101);
print("Dart: Created MyDartResourceHolder: $resourceHolder");
// 将Dart对象和C资源信息注册到Finalizer
// 注意:需要手动分配C字符串内存
final cResourceName = "DB_CONN_101".toNativeUtf8();
registerFinalizer(resourceHolder, resourceHolder.id, cResourceName);
malloc.free(cResourceName); // 立即释放C字符串内存,因为C函数已经复制了它
print("Dart: Registered Finalizer. Checking C resource status.");
checkCResourceStatus();
// 清除Dart代码对resourceHolder的引用,使其成为GC候选者
resourceHolder = MyDartResourceHolder("Another resource", 202); // 替换对象
print("Dart: Cleared Dart's reference to original object. It's now GC eligible.");
// 等待一段时间,希望GC运行并触发Finalizer
// GC行为是不可预测的,可能需要多次尝试或更长时间
for (int i = 0; i < 5; i++) {
print("Dart: Waiting for GC... ($i/5)");
await Future.delayed(Duration(seconds: 1));
checkCResourceStatus(); // 检查C资源是否已被清理
}
print("Dart: Final checks after potential GC.");
checkCResourceStatus();
shutdownFinalizerService(); // 关闭Finalizer服务
print("Dart: Application finished.");
}
Dart_FinalizerService 与 Dart_WeakPersistentHandle 对比
| 特性 | Dart_WeakPersistentHandle |
Dart_FinalizerService / Dart_Finalizer |
|---|---|---|
| 设计哲学 | 弱引用及其一次性终结器回调 | 独立服务管理终结器,允许多个终结器注册 |
| 终结器回调 | Dart_WeakPersistentHandleFinalizer,直接关联到句柄 |
Dart_NewFinalizerService 注册的回调,集中处理 |
| 并发模型 | 终结器在GC线程中执行,可能需要外部同步 | 回调在独立的FinalizerService隔离中执行,隔离性更好,但仍需考虑跨隔离通信 |
peer 数据 |
void* peer,不透明,需要手动类型转换和内存管理 |
void* peer,与Finalizer绑定,清理逻辑集中在回调中 |
| 内存追踪 | 无内置机制告知GC peer 内存大小 |
external_allocation_size 参数可告知GC外部内存大小 |
| 句柄生命周期 | 需要手动 Dart_NewWeakPersistentHandle 和 Dart_DeleteWeakPersistentHandle |
Dart_NewFinalizer 和 Dart_DeleteFinalizer,服务自动管理 |
| 灵活性 | 较简单,适用于单个弱引用场景 | 更强大,适用于多个资源的复杂生命周期管理 |
总而言之,Dart_FinalizerService 和 Dart_Finalizer 提供了更现代、更健壮的终结器机制,是管理Dart对象与C/C++资源生命周期同步的首选方案,尤其适用于需要自动清理外部资源以避免内存泄漏的场景。
内存屏障的视角:跨运行时内存可见性与并发安全
当提到“内存屏障”(Memory Barrier)时,我们通常会联想到CPU架构层面为了保证多处理器或多核系统下内存操作顺序和可见性而采取的同步机制。这些屏障指令(如x86上的mfence、sfence、lfence或ARM上的DMB)确保了指令重排不会跨越屏障,以及缓存中的数据能够被刷新或失效,从而保证不同处理器核心对共享内存的视图一致。
在Dart FFI的上下文中,讨论“内存屏障”具有双重含义:
- GC可见性屏障(FFI句柄的本质):这是本文迄今为止讨论的重点。Dart FFI的句柄机制(
Dart_PersistentHandle,Dart_WeakPersistentHandle,Dart_Finalizer)本身就是一种“逻辑内存屏障”。它们确保Dart垃圾回收器能够“看到”C/C++代码对Dart对象的引用,从而阻止对象被过早回收。这是一种跨运行时(Dart VM与C/C++程序)的内存可见性保障,它不是关于CPU缓存一致性,而是关于GC对引用图的完整性视图。 - 并发访问屏障(传统意义上的内存屏障):当C/C++内存中存储的Dart对象句柄(或其关联的C/C++数据)被多线程并发访问时,传统的内存屏障或原子操作就变得至关重要。这确保了在C/C++代码内部,以及C/C++代码与Dart代码之间共享内存时的读写一致性和可见性。
1. GC可见性屏障:句柄机制的深层含义
我们已经详细探讨了Dart_PersistentHandle如何通过向Dart GC注册一个强引用来阻止对象被回收,以及Dart_WeakPersistentHandle和Dart_Finalizer如何在对象被回收时提供通知。这些机制的本质,就是建立了一个跨越Dart VM和C/C++运行时边界的“可见性屏障”。
- 没有屏障的后果:如果没有这些句柄,Dart GC在遍历其内部引用图时,无法“看到”C/C++内存中的裸指针引用。对于GC来说,这些对象是不可达的,因此可以被安全回收或移动。
- 句柄作为屏障:当C/C++代码创建并持有一个
Dart_PersistentHandle时,它实际上是在Dart VM的内部数据结构中注册了一个“外部根”。这个外部根成为了GC遍历的起点之一,从而使得GC能够“看到”并保留被引用的Dart对象。这种注册行为就是一种GC可见性屏障,它使得C/C++对Dart对象的意图对GC可见。 - 弱引用和终结器:
Dart_WeakPersistentHandle和Dart_Finalizer则提供了另一种形式的GC可见性屏障。它们允许C/C++代码在不阻止GC的情况下“观察”Dart对象的生命周期。当GC决定回收一个只被弱引用引用的对象时,这个“屏障”会触发一个回调,通知C/C++代码对象已被回收,从而允许C/C++清理其关联资源。
因此,从FFI内存安全的角度看,Dart FFI的句柄机制是解决GC可见性问题的核心“内存屏障”。
2. 并发访问屏障:多线程共享内存的挑战
虽然句柄机制解决了GC可见性问题,但如果C/C++内存中存储的这些句柄(或其关联的C/C++数据)本身被多个C/C++线程并发访问,或者被Dart线程和C/C++线程共享访问,那么传统的并发安全问题就会浮现。
例如,一个C/C++全局变量存储了一个Dart_PersistentHandle。如果多个C/C++线程同时尝试读取或修改这个全局变量,就需要同步机制来防止数据竞争。
-
C/C++多线程访问同一块内存:
- 如果C/C++代码在一个多线程环境中管理FFI句柄(例如,一个线程创建句柄,另一个线程删除句柄,或者多个线程并发访问包含句柄的共享数据结构),那么标准的C/C++并发原语(互斥锁
std::mutex、读写锁std::shared_mutex、原子操作std::atomic等)是必不可少的。 std::atomic类型及其内存序(memory order)提供了细粒度的控制,可以实现比互斥锁更高效的同步。例如,std::atomic<Dart_PersistentHandle>可以确保对句柄指针本身的读写是原子的。
// C++ 代码示例:使用 std::atomic 存储 Dart_PersistentHandle #include <atomic> #include <thread> #include <vector> #include <iostream> #include <dart_api.h> // Dart FFI API // 使用 std::atomic 包装 Dart_PersistentHandle,确保原子读写 std::atomic<Dart_PersistentHandle> g_atomic_handle; std::atomic_bool g_shutdown_requested(false); // 假设这是FFI导出的函数 extern "C" DART_EXPORT void set_atomic_handle(Dart_Handle dart_object_handle) { // 创建新的持久句柄 Dart_PersistentHandle new_handle = Dart_NewPersistentHandle(dart_object_handle); if (Dart_IsError(new_handle)) { fprintf(stderr, "Error creating persistent handle.n"); return; } // 原子地替换全局句柄 Dart_PersistentHandle old_handle = g_atomic_handle.exchange(new_handle, std::memory_order_acq_rel); if (old_handle != NULL) { Dart_DeletePersistentHandle(old_handle); // 释放旧句柄 } std::cout << "C: Atomic handle set to " << (void*)new_handle << std::endl; } extern "C" DART_EXPORT void access_atomic_handle() { // 原子地读取全局句柄 Dart_PersistentHandle current_handle = g_atomic_handle.load(std::memory_order_acquire); if (current_handle != NULL) { Dart_Handle temp_handle = Dart_HandleFromPersistent(current_handle); if (!Dart_IsError(temp_handle)) { Dart_Handle string_handle = Dart_ToString(temp_handle); const char* object_str; Dart_StringToCString(string_handle, &object_str); std::cout << "C: Accessed atomic handle: " << object_str << std::endl; } else { std::cerr << "C: Error accessing handle: " << Dart_GetError(temp_handle) << std::endl; } } else { std::cout << "C: No atomic handle set." << std::endl; } } extern "C" DART_EXPORT void clear_atomic_handle() { // 原子地清除全局句柄 Dart_PersistentHandle old_handle = g_atomic_handle.exchange(NULL - 如果C/C++代码在一个多线程环境中管理FFI句柄(例如,一个线程创建句柄,另一个线程删除句柄,或者多个线程并发访问包含句柄的共享数据结构),那么标准的C/C++并发原语(互斥锁