Dart 暴露 C API:为 Native 代码提供 Dart 对象的引用与操作

各位尊敬的开发者,下午好!

今天,我们将深入探讨一个激动人心且极具挑战性的话题:如何让原生(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 的特性:

  1. 瞬时性 (Transient): Dart_Handle 仅在当前 Dart_Scope(或称为“句柄作用域”)内有效。一旦 Dart_Scope 退出,该作用域内创建的所有 Dart_Handle 都会失效,它们所引用的 Dart 对象可能会被垃圾回收。
  2. 错误指示 (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 的 NativeCallableDart_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);

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注