各位尊敬的开发者,下午好!
今天,我们将深入探讨一个激动人心且极具挑战性的话题:如何让原生(Native)代码能够持有并操作 Dart 对象。这不仅仅是 Dart FFI(Foreign Function Interface)的简单反向调用,更是深入到 Dart VM(虚拟机)内部 C API 的艺术与科学。我们将揭开 Dart 运行时如何向原生世界暴露其核心机制的神秘面纱,让 C/C++ 等原生语言能够像 Dart 本身一样,管理和利用 Dart 对象。
作为一名编程专家,我深知跨语言交互的复杂性与必要性。Dart FFI 极大地简化了 Dart 调用 C 函数的流程,但当我们需要将 Dart 对象的引用传递给 C,并期望 C 代码能够读取其属性、调用其方法,乃至在不确定的将来某个时刻对其进行操作时,事情就变得不那么直观了。这正是 Dart VM C API 的用武之地。
本次讲座,我将以丰富的代码示例和严谨的逻辑,带领大家一步步掌握这一强大能力。我们将从 Dart VM C API 的基本概念讲起,逐步深入到对象的生命周期管理、线程安全、错误处理,并通过一个综合性的实战案例,彻底理解其运作机制。
开篇引言:跨越语言的鸿沟
在现代软件开发中,多语言混合编程已是常态。Dart 语言以其高效、易学、跨平台的特性,在移动、Web 和桌面应用开发领域异军突起。然而,任何一门语言都无法涵盖所有场景。我们时常需要利用 C/C++ 编写的高性能库、操作系统原生 API 或遗留代码。Dart FFI 为此提供了一座坚实的桥梁,允许 Dart 代码直接调用原生函数。
但是,这座桥梁通常是单向的:Dart 调用 C。设想一下,如果我们的原生库需要长期持有一个 Dart 对象的引用,并在后台线程中对其进行异步操作;或者,原生代码在处理某些事件后,需要创建一个新的 Dart 对象并将其传递回 Dart 运行时;再或者,Dart 对象所持有的原生资源,需要在 Dart 对象被垃圾回收时自动释放。这些场景都要求原生代码能够“看到”并“理解”Dart 对象,而这正是 Dart VM C API 的核心价值。
Dart VM C API (dart_api.h) 是 Dart VM 提供的底层接口,它允许嵌入 Dart VM 的宿主程序(如 Flutter 引擎、Dart Standalone 运行时本身,或者我们今天探讨的 FFI 场景下的原生库)直接与 Dart 运行时进行交互。通过这些 API,原生代码可以:
- 获取 Dart 对象的引用。
- 访问 Dart 对象的字段。
- 调用 Dart 对象的方法。
- 创建新的 Dart 对象。
- 管理 Dart 对象的生命周期,防止被垃圾回收。
- 安全地在不同线程间传递 Dart 对象。
- 将原生资源与 Dart 对象的生命周期绑定。
理解并正确使用这些 API,是构建高性能、高可靠性 Dart 与原生混合应用的基石。
Dart VM C API:原生代码的基石
Dart VM C API 是一组用 C 语言编写的函数和类型,它们定义了与 Dart 虚拟机交互的标准方式。要使用这些 API,你的原生代码需要包含 dart_api.h 头文件,并且链接到 Dart VM 提供的库。
Dart_Handle:Dart 对象的瞬时引用
在 Dart VM C API 中,最核心的概念之一是 Dart_Handle。它是一个不透明的指针类型 (void*),用于表示对 Dart 对象的引用。每次 Dart VM C API 函数返回一个 Dart 对象时,它都会返回一个 Dart_Handle。
Dart_Handle 的特性:
- 瞬时性 (Transient):
Dart_Handle仅在当前Dart_Scope(或称为“句柄作用域”)内有效。一旦Dart_Scope退出,该作用域内创建的所有Dart_Handle都会失效,它们所引用的 Dart 对象可能会被垃圾回收。 - 错误指示 (Error Indicator):
Dart_Handle不仅仅是一个对象引用,它还可能表示一个错误。如果一个 API 调用失败,它将返回一个表示错误的Dart_Handle。你需要使用Dart_IsError()函数来检查这一点。
Dart_Scope:句柄作用域管理
为了管理 Dart_Handle 的生命周期,Dart VM 引入了 Dart_Scope 的概念。一个 Dart_Scope 是一个栈式分配的区域,用于存储 Dart_Handle。当进入一个 Dart_Scope 时,所有新创建的 Dart_Handle 都属于这个作用域。当退出该作用域时,所有关联的 Dart_Handle 都会自动失效。
为什么需要 Dart_Scope?
Dart VM 是一个垃圾回收系统。如果原生代码能够随意创建和持有 Dart 对象的引用而不进行管理,那么垃圾回收器将无法确定何时可以安全地回收这些对象,可能导致内存泄漏。Dart_Scope 提供了一种机制,让 VM 能够跟踪原生代码所持有的瞬时引用,并在适当的时候清理它们。
使用 Dart_Scope:
Dart_EnterScope():进入一个新的句柄作用域。Dart_ExitScope():退出当前句柄作用域。
重要提示: 任何直接调用 Dart VM C API 的原生函数,都必须在其执行期间处于一个 Dart_Scope 内。FII 通过 Dart_SetNativeResolver 注册的原生函数,如果其第三个参数 auto_setup_scope 设置为 true,VM 会自动在函数入口处调用 Dart_EnterScope() 并在出口处调用 Dart_ExitScope()。对于直接通过 DynamicLibrary.lookupFunction 调用的 FFI 函数,你需要手动在 C 代码中管理 Dart_Scope。
错误处理
Dart_Handle 不仅用于表示 Dart 对象,还用于表示操作过程中发生的错误。dart_api.h 提供了一系列函数来检查和处理错误:
Dart_IsError(handle):检查handle是否表示一个错误。Dart_ErrorGetDescription(handle):获取错误描述字符串。Dart_ThrowException(message_handle):在 Dart 运行时中抛出一个异常。
良好的实践: 在每次调用可能返回错误 Dart_Handle 的 API 后,都应该立即检查其返回值。
// helper.h (or similar, for common utilities)
#ifndef DART_API_HELPER_H
#define DART_API_HELPER_H
#include <dart_api.h>
#include <stdio.h>
#include <stdbool.h>
// 辅助函数:检查 Dart_Handle 是否为错误,并打印错误信息
static bool CheckError(Dart_Handle handle) {
if (Dart_IsError(handle)) {
fprintf(stderr, "Dart API Error: %sn", Dart_ErrorGetDescription(handle));
return true;
}
return false;
}
#endif // DART_API_HELPER_H
Dart 对象在 C 语言中的表示与生命周期管理
Dart_Handle 的瞬时性决定了它不能用于长期持有 Dart 对象的引用。如果原生代码需要在跨越 Dart_Scope 或长时间内保留对 Dart 对象的引用,我们需要使用更强大的机制。
Dart_PersistentHandle:持久引用
Dart_PersistentHandle 是一种持久化的 Dart_Handle。它确保 Dart 对象在被持有期间不会被垃圾回收。当原生代码不再需要这个对象时,必须显式地删除 Dart_PersistentHandle,否则会导致内存泄漏。
使用场景:
- 原生代码需要缓存一个 Dart 对象,以供后续多次访问。
- 原生代码在后台线程中异步操作一个 Dart 对象。
- 原生代码需要将 Dart 对象作为回调函数的参数传递给未来的 Dart 调用。
管理 Dart_PersistentHandle:
Dart_NewPersistentHandle(object_handle):创建一个新的持久句柄,引用object_handle所指向的 Dart 对象。返回的Dart_PersistentHandle是一个不透明的指针 (void*)。Dart_HandleFromPersistent(persistent_handle):从一个Dart_PersistentHandle获取其对应的瞬时Dart_Handle。这个瞬时句柄在当前Dart_Scope内有效。Dart_DeletePersistentHandle(persistent_handle):删除一个持久句柄。这是至关重要的一步,必须在不再需要时调用。
示例:C 语言中创建和使用 Dart_PersistentHandle
// my_native_lib.c (excerpt)
#include "dart_api.h"
#include "dart_native_api.h" // For Dart_CObject
#include "helper.h" // Our error checking helper
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 全局持久句柄,用于演示长期持有
Dart_PersistentHandle global_my_dart_object_handle = NULL;
Dart_Port global_dart_callback_port = ILLEGAL_PORT; // 用于异步回调的 Dart SendPort
// FFI wrapper for Dart_NewPersistentHandle
Dart_PersistentHandle dart_new_persistent_handle_ffi(Dart_Handle object) {
Dart_EnterScope(); // 确保在 Dart Scope 内
Dart_PersistentHandle handle = Dart_NewPersistentHandle(object);
if (CheckError(handle)) {
fprintf(stderr, "C (FFI wrapper): Error creating persistent handle.n");
Dart_ExitScope();
return NULL; // 返回 NULL 表示错误
}
Dart_ExitScope();
return handle; // 返回不透明的句柄指针
}
// FFI wrapper for Dart_DeletePersistentHandle
void dart_delete_persistent_handle_ffi(Dart_PersistentHandle handle) {
if (handle != NULL) {
Dart_EnterScope(); // 确保在 Dart Scope 内
Dart_DeletePersistentHandle(handle);
Dart_ExitScope();
printf("C (FFI wrapper): Deleted persistent handle.n");
}
}
// ... 其他原生函数 ...
Dart_WeakPersistentHandle:弱引用,GC 友好
Dart_WeakPersistentHandle 是一种弱持久句柄。它允许原生代码持有 Dart 对象的引用,但不会阻止 Dart 垃圾回收器回收该对象。如果 Dart 对象变得不可达,即使存在弱持久句柄,它仍然会被回收。当对象被回收时,弱持久句柄会自动失效。
使用场景:
- 原生代码需要缓存 Dart 对象的元数据,但如果 Dart 对象不再被使用,这些元数据也就不重要了。
- 避免循环引用导致的内存泄漏,尤其是在 Dart 对象和原生资源相互引用时。
管理 Dart_WeakPersistentHandle:
Dart_NewWeakPersistentHandle(object_handle, peer, callback):创建一个弱持久句柄。peer是一个原生指针,callback是一个回调函数,当 Dart 对象被回收时,callback会被调用,以便原生代码可以清理peer。Dart_HandleFromWeakPersistent(weak_handle):从弱持久句柄获取瞬时Dart_Handle。如果对象已被回收,则返回Dart_Null()。Dart_DeleteWeakPersistentHandle(weak_handle):删除弱持久句柄。
由于 Dart_WeakPersistentHandle 引入了回调机制,其使用比 Dart_PersistentHandle 更复杂,且通常用于更高级的场景。在大多数情况下,Dart_PersistentHandle 配合严格的生命周期管理已足够。
Dart_FinalizableHandle:关联原生资源与 Dart 对象生命周期
Dart_FinalizableHandle 是一种特殊的持久句柄,它允许将一个原生资源(例如,通过 malloc 分配的内存、文件句柄、数据库连接等)与一个 Dart 对象的生命周期关联起来。当 Dart 对象被垃圾回收时,VM 会自动调用一个指定的原生回调函数来释放关联的原生资源。
这对于 Dart 与原生代码交互,特别是涉及原生资源管理时,至关重要。 它解决了 Dart 对象持有的原生资源可能因 Dart 对象被回收而无法释放的问题。
管理 Dart_FinalizableHandle:
Dart_NewFinalizableHandle(object_handle, peer, external_allocation_size, callback):object_handle:要监视的 Dart 对象。peer:原生资源的指针。external_allocation_size:原生资源的大小(用于 VM 的内存统计)。callback:一个原生回调函数,其签名通常为void (*callback)(void* peer)。当object_handle指向的 Dart 对象被 GC 时,此函数会被调用,并传入peer参数,以便清理原生资源。
Dart_PostExternalResourceFinalizer(peer, callback):这是一个更通用的机制,用于注册一个原生资源终结器,不直接关联 Dart 对象,而是由 VM 在某个时机执行,通常用于嵌入式 Dart 应用。对于 FFI 场景,Dart_NewFinalizableHandle更为常用。
示例:在 C 语言中创建 Dart_FinalizableHandle
// my_native_lib.c (excerpt)
// ... (includes, helper.h) ...
// 一个简单的原生资源结构
typedef struct {
int id;
char* name;
} NativeResource;
// 分配原生资源的函数
NativeResource* allocate_native_resource(int id, const char* name) {
NativeResource* res = (NativeResource*)malloc(sizeof(NativeResource));
if (res) {
res->id = id;
res->name = strdup(name);
printf("C: NativeResource %d (%s) allocated.n", id, name);
}
return res;
}
// 释放原生资源的终结器回调函数
void free_native_resource(void* resource_ptr) {
NativeResource* res = (NativeResource*)resource_ptr;
if (res) {
printf("C: NativeResource %d (%s) freed.n", res->id, res->name);
free(res->name);
free(res);
}
}
// Native function to create a Dart object that is associated with a native resource
// Arguments: [0] = int id, [1] = String name
void create_finalizable_dart_object(Dart_NativeArguments arguments) {
Dart_EnterScope();
Dart_Handle id_handle = Dart_GetNativeArgument(arguments, 0);
int64_t id;
if (CheckError(id_handle) || CheckError(Dart_IntegerToInt64(id_handle, &id))) {
Dart_ThrowException(Dart_NewStringFromCString("Invalid ID argument."));
Dart_ExitScope(); return;
}
Dart_Handle name_handle = Dart_GetNativeArgument(arguments, 1);
const char* name_cstr;
if (CheckError(name_handle) || CheckError(Dart_StringToCString(name_handle, &name_cstr))) {
Dart_ThrowException(Dart_NewStringFromCString("Invalid name argument."));
Dart_ExitScope(); return;
}
// 1. Allocate native resource
NativeResource* res = allocate_native_resource((int)id, name_cstr);
if (!res) {
Dart_ThrowException(Dart_NewStringFromCString("Failed to allocate native resource."));
Dart_ExitScope(); return;
}
// 2. Lookup the Dart class (e.g., 'ResourceWrapper')
Dart_Handle library_root = Dart_LookupLibrary(Dart_NewStringFromCString("package:my_native_app/my_native_app.dart"));
if (CheckError(library_root)) { free_native_resource(res); Dart_ExitScope(); return; }
Dart_Handle class_name_wrapper = Dart_NewStringFromCString("ResourceWrapper");
Dart_Handle wrapper_class = Dart_GetClass(library_root, class_name_wrapper);
if (CheckError(wrapper_class)) { free_native_resource(res); Dart_ExitScope(); return; }
// 3. Create an instance of 'ResourceWrapper' (assuming default constructor)
Dart_Handle new_wrapper_object = Dart_New(wrapper_class, Dart_NewStringFromCString(""), 0, NULL);
if (CheckError(new_wrapper_object)) { free_native_resource(res); Dart_ExitScope(); return; }
// 4. Associate the native resource with the Dart object using a FinalizableHandle
// 当 new_wrapper_object 被 GC 时,free_native_resource 会被调用
Dart_FinalizableHandle finalizable_handle = Dart_NewFinalizableHandle(
new_wrapper_object, // 要监视的 Dart 对象
(void*)res, // 要释放的原生资源指针
sizeof(NativeResource), // 原生资源大小
free_native_resource // 原生终结器回调函数
);
if (CheckError(finalizable_handle)) {
free_native_resource(res); // 如果创建 FinalizableHandle 失败,手动释放
Dart_ThrowException(Dart_NewStringFromCString("Failed to create FinalizableHandle."));
Dart_ExitScope(); return;
}
printf("C: Created FinalizableHandle for ResourceWrapper (ID: %lld).n", id);
// 5. 设置 Dart 函数的返回值
Dart_SetReturnValue(arguments, new_wrapper_object);
Dart_ExitScope();
}
// ...
从 Dart 传递对象到 C:FFI 与 Dart_CObject 的桥梁
Dart 与 C 代码之间的对象传递是双向的。我们已经了解了 C 如何管理 Dart 对象的引用,现在来看看如何实际将 Dart 对象传递到 C 中。
FFI 回调中的 Handle 类型
当使用 Dart FFI 的 NativeCallable 或 Dart_SetNativeResolver 注册的 C 函数时,如果 Dart 函数签名中使用了 Object 类型,那么在 C 侧对应的原生函数将接收到一个 Dart_Handle。这个 Dart_Handle 是一个瞬时句柄,仅在当前 C 函数的执行期间有效。
Dart 侧 FFI 签名:
@Native<Void Function(Handle)>()
external void nativeProcessDartObjectDirect(MyDartObject obj);
@Native<Void Function(Int64, Handle)>()
external void registerDartCallbackAndObject(int sendPort, MyDartObject obj);
C 侧 Dart_NativeArguments 函数签名:
对于通过 Dart_SetNativeResolver 注册的原生函数,其签名必须是 void (*)(Dart_NativeArguments arguments)。通过 Dart_GetNativeArgument(arguments, index) 可以获取传入的 Dart 对象的 Dart_Handle。
// C: nativeProcessDartObjectDirect 接收一个 Dart_Handle
void native_process_dart_object_direct(Dart_NativeArguments arguments) {
Dart_EnterScope(); // 或由 Dart VM 自动管理
Dart_Handle dart_object = Dart_GetNativeArgument(arguments, 0);
if (CheckError(dart_object)) { Dart_ExitScope(); return; }
// 现在 dart_object 是一个瞬时 Dart_Handle,可以在此作用域内操作它
// ...
Dart_ExitScope();
}
// C: register_dart_callback_and_object 接收一个 SendPort (作为 int64) 和一个 MyDartObject (作为 Handle)
void register_dart_callback_and_object(Dart_NativeArguments arguments) {
Dart_EnterScope();
// 获取 SendPort
Dart_Handle send_port_handle = Dart_GetNativeArgument(arguments, 0);
int64_t port_value;
if (CheckError(send_port_handle) || CheckError(Dart_IntegerToInt64(send_port_handle, &port_value))) {
Dart_ExitScope(); return;
}
global_dart_callback_port = (Dart_Port)port_value;
// 获取 Dart 对象
Dart_Handle dart_object = Dart_GetNativeArgument(arguments, 1);
if (CheckError(dart_object)) { Dart_ExitScope(); return; }
// 创建持久句柄
global_my_dart_object_handle = Dart_NewPersistentHandle(dart_object);
if (CheckError(global_my_dart_object_handle)) { Dart_ExitScope(); return; }
Dart_ExitScope();
}
Dart_CObject 及其在异步通信中的应用
Dart_CObject 是一种结构化数据类型,用于在 Dart Isolate 之间或 Dart Isolate 与原生线程之间安全地传递数据。它支持基本类型、字符串、列表、字节数组,以及非常关键的 Dart_CObject_kDart_Object 类型,可以用来传递 Dart 对象的引用(作为 Dart_Handle)。
主要用于:
- 跨 Isolate 通信: 使用
Dart_PostCObject()或Dart_PostCObject_DLFCN()将Dart_CObject发送到另一个 Isolate 的ReceivePort。 - 原生线程向 Dart Isolate 发送消息: 原生线程可以在不进入
Dart_Scope的情况下,构建一个Dart_CObject并使用Dart_PostCObject()发送给 Dart Isolate。
Dart_CObject 结构体:
typedef struct _Dart_CObject {
Dart_CObject_Type type;
union {
bool as_bool;
int32_t as_int32;
int64_t as_int64;
double as_double;
struct {
char* chars;
intptr_t length;
} as_string;
struct {
Dart_CObject** values;
intptr_t length;
} as_array;
struct {
Dart_TypedData_Type type;
uint8_t* values;
intptr_t length;
} as_typed_data;
// ... 其他类型 ...
Dart_Handle as_dart_object; // 用于传递 Dart 对象的引用
} value;
} Dart_CObject;
示例:C 代码通过 Dart_CObject 将 Dart 对象异步发送回 Dart
// my_native_lib.c (excerpt)
// ... (includes, helper.h) ...
// Native function to trigger the Dart callback and pass back a new Dart object
void trigger_dart_callback(Dart_NativeArguments arguments) {
if (global_dart_callback_port == ILLEGAL_PORT) {
fprintf(stderr, "C: Error: Dart callback port not registered.n");
return;
}
Dart_EnterScope(); // 确保在 Dart Scope 内
// 1. 创建一个新的 Dart 对象(例如,Message 类的实例)
Dart_Handle library_root = Dart_LookupLibrary(Dart_NewStringFromCString("package:my_native_app/my_native_app.dart"));
if (CheckError(library_root)) { Dart_ExitScope(); return; }
Dart_Handle class_name_msg = Dart_NewStringFromCString("Message");
Dart_Handle message_class = Dart_GetClass(library_root, class_name_msg);
if (CheckError(message_class)) { Dart_ExitScope(); return; }
Dart_Handle constructor_name = Dart_NewStringFromCString(""); // 默认构造函数
Dart_Handle arg1 = Dart_NewStringFromCString("Hello from C!");
Dart_Handle arg2 = Dart_NewInteger(12345);
Dart_Handle constructor_args[] = { arg1, arg2 };
int num_args = sizeof(constructor_args) / sizeof(constructor_args[0]);
Dart_Handle new_message_object = Dart_New(message_class, constructor_name, num_args, constructor_args);