Handle Scope 管理:在 FFI 中操作 Dart Persistent Handle 的最佳实践
大家好,今天我们来深入探讨一个在 Dart FFI(Foreign Function Interface)中至关重要的概念:Handle Scope 管理。在使用 FFI 时,我们经常需要在 Dart 和本地代码之间传递对象。为了确保这些对象在本地代码中能够安全、有效地被访问,Dart 提供了 Persistent Handle 机制。而 Handle Scope 则是在使用 Persistent Handle 时进行资源管理的关键手段。
什么是 Persistent Handle 和 Handle Scope?
在 FFI 中,Dart 对象不能直接传递给本地代码,因为它们的内存管理由 Dart VM 控制。如果本地代码直接持有 Dart 对象的指针,那么当 Dart VM 进行垃圾回收时,可能会导致本地代码访问到无效的内存地址,从而引发崩溃。
为了解决这个问题,Dart 提供了 Persistent Handle。 Persistent Handle 本质上是 Dart VM 对 Dart 对象的引用,它告诉 Dart VM 不要回收该对象,即使它不再被 Dart 代码引用。 本地代码可以通过 Persistent Handle 间接地访问 Dart 对象,而不用担心垃圾回收的问题。
Handle Scope 则是一种用于管理 Persistent Handle 生命周期的机制。 它可以确保 Persistent Handle 在不再需要时能够及时释放,避免内存泄漏。Handle Scope 遵循 RAII (Resource Acquisition Is Initialization) 原则,即资源的获取与对象的生命周期绑定。 当 Handle Scope 对象被创建时,它会创建一个新的 Handle Scope;当 Handle Scope 对象被销毁时,它会自动释放该 Scope 内创建的所有 Persistent Handle。
Persistent Handle 的类型
Dart FFI 提供了两种类型的 Persistent Handle:
- Normal Persistent Handle: 用于存储 Dart 对象的引用。
- Finalizable Persistent Handle: 除了存储 Dart 对象的引用外,还允许你注册一个 finalizer 函数。当该 Handle 对应的 Dart 对象被垃圾回收时,finalizer 函数会被调用。这对于释放本地代码持有的资源非常有用。
Handle Scope 的类型
Dart FFI 提供了两种 Handle Scope:
- Dart_HandleScope: 用于创建和管理 Normal Persistent Handle。
- Dart_FinalizableHandleScope: 用于创建和管理 Finalizable Persistent Handle。
Handle Scope 的使用方法
下面我们通过一些代码示例来演示 Handle Scope 的使用方法。
示例 1:使用 Dart_HandleScope 创建和释放 Persistent Handle
#include <dart_api.h>
#include <dart_api_dl.h>
Dart_NativeIntegerType fibonacci(Dart_Handle handle, Dart_NativeIntegerType n) {
Dart_HandleScope scope;
Dart_EnterScope(&scope);
// 创建一个 Dart Integer 对象
Dart_Handle dart_n = Dart_NewInteger(n);
// 将 Dart Integer 对象转换为 Persistent Handle
Dart_Handle persistent_handle = Dart_NewPersistentHandle(dart_n);
// 模拟一些操作,使用 persistent_handle 访问 Dart 对象
// ...
// 释放 Persistent Handle
Dart_DeletePersistentHandle(persistent_handle);
Dart_ExitScope(&scope);
// 计算斐波那契数列
if (n <= 1) {
return n;
}
return fibonacci(handle, n - 1) + fibonacci(handle, n - 2);
}
Dart_Handle HandleFibonacci(Dart_Handle handle, Dart_NativeArguments arguments) {
Dart_EnterScope();
Dart_Handle n_obj = Dart_GetNativeArgument(arguments, 0);
if (Dart_IsError(n_obj)) {
return n_obj;
}
int64_t n;
Dart_IntegerToInt64(n_obj, &n);
Dart_NativeIntegerType result = fibonacci(handle, n);
Dart_Handle result_obj = Dart_NewInteger(result);
if (Dart_IsError(result_obj)) {
Dart_ExitScope();
return result_obj;
}
Dart_SetReturnValue(arguments, result_obj);
Dart_ExitScope();
return Dart_Null();
}
在这个例子中,我们使用 Dart_HandleScope 来管理一个 Persistent Handle。首先,我们使用 Dart_EnterScope 创建一个 Handle Scope。然后,我们使用 Dart_NewPersistentHandle 创建一个 Persistent Handle,指向一个 Dart Integer 对象。在不再需要该 Persistent Handle 时,我们使用 Dart_DeletePersistentHandle 释放它。最后,我们使用 Dart_ExitScope 销毁 Handle Scope。
示例 2:使用 Dart_FinalizableHandleScope 创建和释放 Finalizable Persistent Handle
#include <dart_api.h>
#include <dart_api_dl.h>
// 定义一个 finalizer 函数
void MyFinalizer(void* isolate_callback_data, void* peer) {
// 释放本地资源
delete reinterpret_cast<int*>(peer);
}
Dart_Handle CreateFinalizableObject(Dart_Handle handle, Dart_NativeArguments arguments) {
Dart_FinalizableHandleScope scope;
Dart_EnterFinalizableScope(&scope);
// 创建一个本地资源
int* my_resource = new int(42);
// 创建一个 Dart Object
Dart_Handle my_object = Dart_NewObject(Dart_Null()); // Replace Dart_Null() with a proper Class object if needed.
// 创建一个 Finalizable Persistent Handle,并将本地资源与之关联
Dart_NewFinalizableHandle(
my_object,
my_resource,
sizeof(int),
MyFinalizer);
Dart_SetReturnValue(arguments, my_object);
Dart_ExitFinalizableScope(&scope);
return Dart_Null();
}
在这个例子中,我们使用 Dart_FinalizableHandleScope 来管理一个 Finalizable Persistent Handle。首先,我们使用 Dart_EnterFinalizableScope 创建一个 Finalizable Handle Scope。然后,我们创建一个本地资源 my_resource,并使用 Dart_NewFinalizableHandle 创建一个 Finalizable Persistent Handle,将 my_object 和 my_resource 关联起来。同时,我们还指定了一个 finalizer 函数 MyFinalizer,该函数会在 my_object 被垃圾回收时被调用,用于释放 my_resource。最后,我们使用 Dart_ExitFinalizableScope 销毁 Finalizable Handle Scope。
示例 3:在 Dart 代码中使用 FFI 调用上述函数
import 'dart:ffi';
final dylib = DynamicLibrary.open('path/to/your/library.so'); // Replace with your library path
// Define function types
typedef CreateFinalizableObjectFunc = Pointer Function(Pointer, Pointer);
typedef HandleFibonacciFunc = Pointer Function(Pointer, Pointer);
// Obtain function pointers
final createFinalizableObject = dylib.lookupFunction<CreateFinalizableObjectFunc, CreateFinalizableObjectFunc>('CreateFinalizableObject');
final handleFibonacci = dylib.lookupFunction<HandleFibonacciFunc, HandleFibonacciFunc>('HandleFibonacci');
void main() {
// Call CreateFinalizableObject
final object = createFinalizableObject(nullptr, nullptr);
// Call HandleFibonacci
final result = handleFibonacci(nullptr, Pointer.fromFunction(HandleFibonacci));
print('Finalizable Object created. Fibonacci result: $result');
// Trigger garbage collection to test the finalizer. This is not guaranteed
// to run immediately.
for (int i = 0; i < 100000; i++) {
List<int> temp = List.filled(1000, i);
}
print('Garbage collection triggered (may not run immediately)');
}
这段 Dart 代码首先加载包含 C++ 函数的动态库。然后,它定义了 C++ 函数的函数类型,并使用 dylib.lookupFunction 获取函数的指针。最后,它调用 CreateFinalizableObject 函数创建一个 Finalizable Persistent Handle,并通过循环分配大量内存来触发垃圾回收,以测试 finalizer 是否被调用。
Handle Scope 的嵌套
Handle Scope 可以嵌套使用。当存在嵌套的 Handle Scope 时,内部的 Handle Scope 会继承外部 Handle Scope 的上下文。这意味着在内部 Handle Scope 中创建的 Persistent Handle 会被添加到内部 Handle Scope 中,并且当内部 Handle Scope 被销毁时,这些 Persistent Handle 会被释放。
#include <dart_api.h>
#include <dart_api_dl.h>
Dart_Handle NestedScopes(Dart_Handle handle, Dart_NativeArguments arguments) {
Dart_HandleScope outer_scope;
Dart_EnterScope(&outer_scope);
// 创建一个 Dart Integer 对象
Dart_Handle outer_object = Dart_NewInteger(10);
// 将 Dart Integer 对象转换为 Persistent Handle (在外部 Scope 中)
Dart_Handle outer_persistent_handle = Dart_NewPersistentHandle(outer_object);
{ // 内部 Handle Scope
Dart_HandleScope inner_scope;
Dart_EnterScope(&inner_scope);
// 创建一个 Dart String 对象
Dart_Handle inner_object = Dart_NewStringFromCString("Hello");
// 将 Dart String 对象转换为 Persistent Handle (在内部 Scope 中)
Dart_Handle inner_persistent_handle = Dart_NewPersistentHandle(inner_object);
// 模拟一些操作
// ...
Dart_DeletePersistentHandle(inner_persistent_handle); // 显式释放,虽然作用域结束时也会自动释放
Dart_ExitScope(&inner_scope); // 内部 Scope 结束,inner_persistent_handle 被释放
}
// 模拟一些操作
// ...
Dart_DeletePersistentHandle(outer_persistent_handle); // 显式释放,虽然作用域结束时也会自动释放
Dart_ExitScope(&outer_scope); // 外部 Scope 结束,outer_persistent_handle 被释放
return Dart_Null();
}
在这个例子中,我们创建了一个外部 Handle Scope 和一个内部 Handle Scope。在外部 Handle Scope 中,我们创建了一个 Dart Integer 对象和一个 Persistent Handle outer_persistent_handle。在内部 Handle Scope 中,我们创建了一个 Dart String 对象和一个 Persistent Handle inner_persistent_handle。当内部 Handle Scope 被销毁时,inner_persistent_handle 会被自动释放。当外部 Handle Scope 被销毁时,outer_persistent_handle 会被自动释放。
Handle Scope 管理的最佳实践
以下是一些 Handle Scope 管理的最佳实践:
- 始终使用 Handle Scope: 在 FFI 代码中,只要涉及到创建或访问 Dart 对象,就应该使用 Handle Scope。
- 尽早创建,尽晚销毁: Handle Scope 的生命周期应该尽可能短。在不再需要访问 Dart 对象时,应该立即销毁 Handle Scope,释放 Persistent Handle。
- 避免 Handle Scope 泄露: 确保每个
Dart_EnterScope或Dart_EnterFinalizableScope都有对应的Dart_ExitScope或Dart_ExitFinalizableScope。 - 使用 Finalizable Persistent Handle 释放本地资源: 如果本地代码持有了 Dart 对象关联的本地资源,应该使用 Finalizable Persistent Handle,并在 finalizer 函数中释放这些资源。
- 显式释放 Persistent Handle: 即使 Handle Scope 会在销毁时自动释放 Persistent Handle,为了代码的可读性和可维护性,建议在不再需要 Persistent Handle 时,显式地调用
Dart_DeletePersistentHandle释放它。 - 注意异常处理: 在使用 Handle Scope 的代码中,要特别注意异常处理。如果在
Dart_EnterScope和Dart_ExitScope之间发生异常,可能会导致 Handle Scope 泄露。可以使用 try-finally 语句来确保Dart_ExitScope始终被调用。
Dart_Handle ExampleWithException(Dart_Handle handle, Dart_NativeArguments arguments) {
Dart_HandleScope scope;
Dart_EnterScope(&scope);
Dart_Handle result = Dart_Null();
try {
// 创建一个 Dart Integer 对象
Dart_Handle dart_n = Dart_NewInteger(42);
// 将 Dart Integer 对象转换为 Persistent Handle
Dart_Handle persistent_handle = Dart_NewPersistentHandle(dart_n);
// 模拟一些操作,可能会抛出异常
if (true) {
throw std::runtime_error("Something went wrong!");
}
// 释放 Persistent Handle
Dart_DeletePersistentHandle(persistent_handle);
result = Dart_True(); // Or some other success value
} catch (const std::exception& e) {
// 异常处理
std::cerr << "Exception: " << e.what() << std::endl;
result = Dart_NewStringFromCString(e.what()); // Return the error as a Dart String. Handle properly in Dart.
} catch (...) {
std::cerr << "Unknown exception!" << std::endl;
result = Dart_NewStringFromCString("Unknown error");
}
Dart_ExitScope(&scope);
return result;
}
总结:Handle Scope 是 FFI 中资源管理的关键
Handle Scope 和 Persistent Handle 是 Dart FFI 中不可或缺的组成部分。通过合理地使用 Handle Scope,可以确保 Dart 对象在本地代码中能够被安全、有效地访问,避免内存泄漏,并提高程序的健壮性。 掌握 Handle Scope 的使用方法和最佳实践,是编写高质量 Dart FFI 代码的关键。正确使用 Handle Scope,避免内存泄漏,保证程序健壮性。
Finalizable Handle 与资源释放
Finalizable Handle 允许你关联本地资源,并在 Dart 对象被回收时释放这些资源。
异常处理与 Handle Scope
在涉及 Handle Scope 的代码中,务必进行异常处理,确保 Handle Scope 始终被正确销毁。