C++ 对象的 Dart 堆引用:实现 Native 对象与 Dart 对象的双向绑定

C++ 对象的 Dart 堆引用:实现 Native 对象与 Dart 对象的双向绑定

在现代软件开发中,跨语言和跨平台的需求日益增长。Dart,作为Flutter框架的核心语言,以其高效的开发体验和优秀的性能,在移动、桌面及Web领域占据一席之地。然而,许多高性能计算、系统级操作或现有复杂库仍基于C或C++构建。为了充分利用这些既有资产,并突破Dart/Flutter纯语言环境的边界,实现C++对象与Dart对象的无缝交互变得至关重要。

本文旨在深入探讨如何在Dart中安全地引用C++堆对象,并进一步实现C++端对Dart对象的反向引用及回调,从而构建一个健壮的双向绑定机制。我们将从Dart FFI的基础开始,逐步深入到内存管理、生命周期同步、异步回调等高级议题,并辅以详尽的代码示例。

1. 跨语言交互的基石:Dart FFI 简介

Dart FFI (Foreign Function Interface) 是Dart语言提供的一种机制,允许Dart代码直接调用C语言(或C兼容的C++)的函数,并访问其数据结构。这为Dart应用集成原生库、利用系统API或与现有C/C++代码库交互提供了可能。

1.1 FFI 的核心概念

  • dart:ffi:所有FFI操作的基础。
  • Pointer<T>:代表一个指向原生内存的指针。T 可以是 Void (通用指针), Int32, Float, Struct 等。
  • NativeFunction<TFunction>:表示一个C函数签名,TFunction 是Dart类型,用于描述C函数的参数和返回值。
  • DynamicLibrary:用于加载动态链接库(如 .dll, .so, .dylib)。
  • lookupFunction<TNativeFunction extends Function, TDartFunction extends Function>(String symbolName):从加载的库中查找并获取一个C函数的Dart封装。

1.2 类型映射

Dart FFI 提供了一套清晰的类型映射规则,将Dart类型转换为C类型,反之亦然。

Dart FFI 类型 C 类型 Dart 类型 (用于函数签名) 备注
Int8 int8_t int
Int16 int16_t int
Int32 int32_t int
Int64 int64_t int
Uint8 uint8_t int
Uint16 uint16_t int
Uint32 uint32_t int
Uint64 uint64_t int
Float float double
Double double double
Bool bool (或C int) bool 实际C类型可能是 charint
Void void void
Pointer<T> T* Pointer<T> 指针类型
Pointer<NativeFunction<T>> 函数指针 T (*func)(...) Pointer<NativeFunction<T>> 用于C函数回调到Dart,或传递函数指针
Array<T> T[] List<T> (或 Pointer<T>) 通常通过 Pointer<T> 和偏移量访问数组元素
Struct 结构体 struct MyStruct MyStruct (Dart FFI Struct 的子类)

1.3 加载动态库与调用C函数

首先,我们需要一个C/C++动态库。为了演示,我们创建一个简单的C++库,其中包含一个C风格的函数。

C++代码 (native_library.h)

#ifndef NATIVE_LIBRARY_H
#define NATIVE_LIBRARY_H

#ifdef _WIN32
  #define EXPORT_API __declspec(dllexport)
#else
  #define EXPORT_API __attribute__((visibility("default")))
#endif

extern "C" {
    EXPORT_API int add(int a, int b);
}

#endif // NATIVE_LIBRARY_H

C++代码 (native_library.cpp)

#include "native_library.h"

extern "C" {
    EXPORT_API int add(int a, int b) {
        return a + b;
    }
}

编译这个库会生成 native_library.dll (Windows), libnative_library.so (Linux) 或 libnative_library.dylib (macOS)。

Dart代码 (main.dart)

import 'dart:ffi';
import 'dart:io';

// 定义C函数签名
typedef AddFunctionC = Int32 Function(Int32 a, Int32 b);
// 定义Dart中对应C函数的签名
typedef AddFunctionDart = int Function(int a, int b);

void main() {
  // 根据平台加载动态库
  DynamicLibrary nativeLib;
  if (Platform.isWindows) {
    nativeLib = DynamicLibrary.open('native_library.dll');
  } else if (Platform.isMacOS) {
    nativeLib = DynamicLibrary.open('libnative_library.dylib');
  } else if (Platform.isLinux) {
    nativeLib = DynamicLibrary.open('libnative_library.so');
  } else {
    throw UnsupportedError('Unsupported platform');
  }

  // 查找并封装C函数
  final add = nativeLib.lookupFunction<AddFunctionC, AddFunctionDart>('add');

  // 调用C函数
  int result = add(10, 20);
  print('10 + 20 = $result'); // 输出: 10 + 20 = 30
}

2. Dart 引用 C++ 对象:单向绑定与内存管理

现在,我们面临一个更复杂的场景:C++类。C++类实例在C++堆上分配,其生命周期由C++运行时管理。Dart的垃圾回收器无法感知这些原生内存。因此,当Dart持有C++对象的引用时,必须确保:

  1. Dart能够安全地调用C++对象的方法。
  2. 当Dart对象被垃圾回收时,对应的C++对象也能被正确销毁,避免内存泄漏。

2.1 C++ 对象的 FFI 友好封装

为了让Dart能够操作C++对象,我们需要在C++端提供一个C风格的API,作为Dart与C++类之间的桥梁。

C++代码 (my_cpp_object.h)

#ifndef MY_CPP_OBJECT_H
#define MY_CPP_OBJECT_H

#include <iostream>
#include <string>

#ifdef _WIN32
  #define EXPORT_API __declspec(dllexport)
#else
  #define EXPORT_API __attribute__((visibility("default")))
#endif

// 实际的C++类
class MyCppObject {
public:
    MyCppObject(int initialValue) : _value(initialValue) {
        std::cout << "MyCppObject constructed with value: " << _value << std::endl;
    }

    ~MyCppObject() {
        std::cout << "MyCppObject destructed. Value was: " << _value << std::endl;
    }

    void increment() {
        _value++;
        std::cout << "MyCppObject value incremented to: " << _value << std::endl;
    }

    int getValue() const {
        return _value;
    }

    void setValue(int newValue) {
        _value = newValue;
        std::cout << "MyCppObject value set to: " << _value << std::endl;
    }

private:
    int _value;
};

// C风格的API,用于Dart FFI
extern "C" {
    // 构造函数:返回一个指向MyCppObject实例的指针
    EXPORT_API MyCppObject* my_cpp_object_create(int initialValue);

    // 析构函数:销毁MyCppObject实例
    EXPORT_API void my_cpp_object_destroy(MyCppObject* obj);

    // 方法:调用MyCppObject::increment()
    EXPORT_API void my_cpp_object_increment(MyCppObject* obj);

    // 方法:调用MyCppObject::getValue()
    EXPORT_API int my_cpp_object_get_value(MyCppObject* obj);

    // 方法:调用MyCppObject::setValue()
    EXPORT_API void my_cpp_object_set_value(MyCppObject* obj, int newValue);
}

#endif // MY_CPP_OBJECT_H

C++代码 (my_cpp_object.cpp)

#include "my_cpp_object.h"

extern "C" {
    MyCppObject* my_cpp_object_create(int initialValue) {
        return new MyCppObject(initialValue);
    }

    void my_cpp_object_destroy(MyCppObject* obj) {
        delete obj;
    }

    void my_cpp_object_increment(MyCppObject* obj) {
        if (obj) {
            obj->increment();
        }
    }

    int my_cpp_object_get_value(MyCppObject* obj) {
        if (obj) {
            return obj->getValue();
        }
        return -1; // 错误处理:返回一个无效值
    }

    void my_cpp_object_set_value(MyCppObject* obj, int newValue) {
        if (obj) {
            obj->setValue(newValue);
        }
    }
}

编译生成动态库 libmy_cpp_object.so (或对应平台的文件)。

2.2 Dart 中的对象封装与生命周期管理

Dart NativeFinalizer 是解决原生资源生命周期管理的关键。它允许你注册一个回调,当一个特定的Dart对象被垃圾回收时,FFI会自动调用这个回调,通常用于释放对应的原生资源。

Dart代码 (main.dart)

import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart'; // 提供了 malloc, free 等工具

// 1. 定义C函数的Dart FFI签名
typedef MyCppObjectCreateC = Pointer<Void> Function(Int32 initialValue);
typedef MyCppObjectCreateDart = Pointer<Void> Function(int initialValue);

typedef MyCppObjectDestroyC = Void Function(Pointer<Void> obj);
typedef MyCppObjectDestroyDart = void Function(Pointer<Void> obj);

typedef MyCppObjectIncrementC = Void Function(Pointer<Void> obj);
typedef MyCppObjectIncrementDart = void Function(Pointer<Void> obj);

typedef MyCppObjectGetValueC = Int32 Function(Pointer<Void> obj);
typedef MyCppObjectGetValueDart = int Function(Pointer<Void> obj);

typedef MyCppObjectSetValueC = Void Function(Pointer<Void> obj, Int32 newValue);
typedef MyCppObjectSetValueDart = void Function(Pointer<Void> obj, int newValue);

// 2. 加载动态库并封装C函数
class NativeBindings {
  late final DynamicLibrary _nativeLib;

  late final MyCppObjectCreateDart myCppObjectCreate;
  late final MyCppObjectDestroyDart myCppObjectDestroy;
  late final MyCppObjectIncrementDart myCppObjectIncrement;
  late final MyCppObjectGetValueDart myCppObjectGetValue;
  late final MyCppObjectSetValueDart myCppObjectSetValue;

  NativeBindings() {
    if (Platform.isWindows) {
      _nativeLib = DynamicLibrary.open('libmy_cpp_object.dll');
    } else if (Platform.isMacOS) {
      _nativeLib = DynamicLibrary.open('libmy_cpp_object.dylib');
    } else if (Platform.isLinux) {
      _nativeLib = DynamicLibrary.open('libmy_cpp_object.so');
    } else {
      throw UnsupportedError('Unsupported platform');
    }

    myCppObjectCreate = _nativeLib
        .lookupFunction<MyCppObjectCreateC, MyCppObjectCreateDart>('my_cpp_object_create');
    myCppObjectDestroy = _nativeLib
        .lookupFunction<MyCppObjectDestroyC, MyCppObjectDestroyDart>('my_cpp_object_destroy');
    myCppObjectIncrement = _nativeLib
        .lookupFunction<MyCppObjectIncrementC, MyCppObjectIncrementDart>('my_cpp_object_increment');
    myCppObjectGetValue = _nativeLib
        .lookupFunction<MyCppObjectGetValueC, MyCppObjectGetValueDart>('my_cpp_object_get_value');
    myCppObjectSetValue = _nativeLib
        .lookupFunction<MyCppObjectSetValueC, MyCppObjectSetValueDart>('my_cpp_object_set_value');
  }
}

// 3. Dart封装类,持有C++对象的指针并管理其生命周期
class DartCppObject {
  // 持有指向C++对象的原生指针
  Pointer<Void> _nativeInstance;
  // FFI绑定实例
  static final NativeBindings _bindings = NativeBindings();

  // NativeFinalizer,用于在Dart对象被GC时自动调用C++析构函数
  // 它需要一个Pointer<NativeFinalizerFunction>,指向一个接受Pointer<Void>参数的C函数
  // 这里我们直接用 myCppObjectDestroy 的函数指针
  static final NativeFinalizer _finalizer =
      NativeFinalizer(_bindings.myCppObjectDestroy.asFunction<void Function(Pointer<Void>)>());

  DartCppObject(int initialValue)
      : _nativeInstance = _bindings.myCppObjectCreate(initialValue) {
    // 注册终结器:当this (DartCppObject实例) 被GC时,调用myCppObjectDestroy并传入_nativeInstance
    _finalizer.attach(this, _nativeInstance, detach: this);
  }

  // 方法代理到C++
  void increment() {
    _bindings.myCppObjectIncrement(_nativeInstance);
  }

  int getValue() {
    return _bindings.myCppObjectGetValue(_nativeInstance);
  }

  void setValue(int newValue) {
    _bindings.myCppObjectSetValue(_nativeInstance, newValue);
  }

  // 显式销毁(可选,但推荐在某些场景下使用,例如资源池)
  void dispose() {
    if (_nativeInstance != nullptr) {
      _bindings.myCppObjectDestroy(_nativeInstance);
      _nativeInstance = nullptr; // 防止重复销毁
      _finalizer.detach(this); // 从终结器中移除,避免再次尝试销毁已释放的内存
      print('DartCppObject explicitly disposed.');
    }
  }

  // 用于演示,实际项目中可能不需要
  @override
  String toString() {
    return 'DartCppObject(nativeAddress: ${_nativeInstance.address}, value: ${getValue()})';
  }
}

void main() async {
  print('--- Creating DartCppObject ---');
  DartCppObject obj1 = DartCppObject(100);
  print('Initial value: ${obj1.getValue()}');
  obj1.increment();
  print('Value after increment: ${obj1.getValue()}');
  obj1.setValue(200);
  print('Value after set: ${obj1.getValue()}');

  print('n--- Testing explicit dispose ---');
  DartCppObject obj2 = DartCppObject(300);
  print('Obj2 value: ${obj2.getValue()}');
  obj2.dispose(); // 显式销毁
  // obj2.getValue(); // 这里访问已销毁的C++对象可能导致崩溃

  print('n--- Testing garbage collection with NativeFinalizer ---');
  // 创建一个局部变量,等待其超出作用域被GC
  void createAndLetGo() {
    DartCppObject obj3 = DartCppObject(400);
    print('Obj3 created with value: ${obj3.getValue()}');
    obj3.increment();
    print('Obj3 value after increment: ${obj3.getValue()}');
  }

  createAndLetGo();
  print('Obj3 should be out of scope now. Waiting for GC...');

  // 强制进行一些操作,增加GC发生的几率,但这不能保证立即发生
  // 在实际应用中,GC是自动的,不应依赖手动触发或等待
  for (int i = 0; i < 100000; i++) {
    List.filled(1000, 0, growable: false); // 制造一些垃圾
  }
  await Future.delayed(Duration(milliseconds: 100)); // 给GC一些时间
  print('End of main. C++ destructors for GC'd objects should have been called.');
}

运行结果示例 (可能因为GC时机不同而略有差异)

--- Creating DartCppObject ---
MyCppObject constructed with value: 100
Initial value: 100
MyCppObject value incremented to: 101
Value after increment: 101
MyCppObject value set to: 200
Value after set: 200

--- Testing explicit dispose ---
MyCppObject constructed with value: 300
Obj2 value: 300
MyCppObject destructed. Value was: 300
DartCppObject explicitly disposed.

--- Testing garbage collection with NativeFinalizer ---
MyCppObject constructed with value: 400
Obj3 created with value: 400
MyCppObject value incremented to: 401
Obj3 value after increment: 401
Obj3 should be out of scope now. Waiting for GC...
End of main. C++ destructors for GC'd objects should have been called.
MyCppObject destructed. Value was: 401

从输出可以看到,obj1 的C++析构函数在程序结束时才被调用(因为它是全局可达的)。obj2 被显式 dispose 后,其C++析构函数立即被调用。obj3createAndLetGo 函数结束后,当Dart的垃圾回收器运行时,其C++析构函数被 NativeFinalizer 触发调用。这表明我们已经成功地将C++对象的生命周期与Dart对象的生命周期关联起来。

关键点:

  • Pointer<Void> 作为不透明句柄在Dart中传递C++对象。
  • NativeFinalizer 是实现 Dart 对象 GC 与 C++ 资源释放同步的关键。它持有对 C 函数的指针,并在 Dart 对象被回收时调用该函数,传入 C++ 对象的指针。
  • _finalizer.attach(this, _nativeInstance, detach: this):第一个参数是Dart对象,第二个参数是原生资源的地址,第三个参数是可选的“脱离令牌”,用于显式 detach
  • 显式 dispose 方法允许在Dart对象被GC之前就释放原生资源,这对于有限资源或需要立即清理的场景很有用。

3. C++ 引用 Dart 对象:双向绑定与异步回调

仅仅是Dart引用C++对象还不够,很多场景下C++也需要能够:

  1. 持有对Dart对象的引用。
  2. 在特定事件发生时,回调Dart代码。

这比单向绑定复杂得多,因为C++不了解Dart的垃圾回收机制,也不能直接调用Dart函数(因为C++可能运行在不同的线程,甚至不同的Isolate)。

3.1 Dart Persistent Handles 和 C++ 回调机制

为了解决C++引用Dart对象的问题,Dart FFI 提供了 Dart_NewPersistentHandleDart_PostCObject 等机制。

  • Dart_NewPersistentHandle(Dart_Handle object):创建一个持久句柄,防止对应的Dart对象被垃圾回收。这个句柄可以在C++代码中安全存储。
  • Dart_DeletePersistentHandle(Dart_PersistentHandle handle):销毁一个持久句柄,允许对应的Dart对象被垃圾回收。
  • Dart_NewWeakPersistentHandle (Dart 2.17+): 创建一个弱持久句柄,它不会阻止Dart对象被GC。当Dart对象被GC时,可以提供一个回调函数,通常用于清理C++侧的对应引用。但使用起来更复杂,这里我们主要聚焦于强持久句柄。
  • *`Dart_PostCObject(Dart_Port port_id, Dart_CObject message)**:这是C++向Dart发送消息的主要机制。port_id是DartSendPort的原生ID,message是一个Dart_CObject` 结构体,用于封装要发送的数据。这个函数可以在任何线程调用,并且是异步安全的。
  • ReceivePort:Dart侧用于接收来自C++消息的机制。它有一个 sendPort 属性,其 nativePort 就是 Dart_Port

3.2 C++ 侧的回调系统设计

我们需要在C++中设计一个机制来存储Dart的 SendPort 和相关的上下文信息,并在需要时通过 Dart_PostCObject 发送消息。

C++代码 (my_cpp_object_with_callback.h)

#ifndef MY_CPP_OBJECT_WITH_CALLBACK_H
#define MY_CPP_OBJECT_WITH_CALLBACK_H

#include <iostream>
#include <string>
#include <vector>
#include <mutex> // For thread safety if callbacks can be triggered from multiple threads

// Dart C API 头文件 (需要从Dart SDK中获取)
// 通常在 Dart SDK/include/dart_api.h
#include "dart_api.h"

#ifdef _WIN32
  #define EXPORT_API __declspec(dllexport)
#else
  #define EXPORT_API __attribute__((visibility("default")))
#endif

// 定义一个结构体来存储Dart回调信息
// 包含了Dart的SendPort ID和可能的上下文数据
struct DartCallbackInfo {
    Dart_Port sendPortId; // Dart ReceivePort的native ID
    // 可以在这里添加其他Dart_PersistentHandle或上下文数据
    // 例如:Dart_PersistentHandle callbackObjectHandle;
};

// 带有回调功能的C++类
class MyCppObjectWithCallback {
public:
    MyCppObjectWithCallback(int initialValue) : _value(initialValue) {
        std::cout << "MyCppObjectWithCallback constructed with value: " << _value << std::endl;
    }

    ~MyCppObjectWithCallback() {
        std::cout << "MyCppObjectWithCallback destructed. Value was: " << _value << std::endl;
        // 清理所有注册的回调
        std::lock_guard<std::mutex> lock(_callbacksMutex);
        _callbacks.clear(); // 这里不涉及Dart_DeletePersistentHandle,因为我们只传递了Port ID
    }

    void increment() {
        _value++;
        std::cout << "MyCppObjectWithCallback value incremented to: " << _value << std::endl;
        notifyValueChange();
    }

    int getValue() const {
        return _value;
    }

    void setValue(int newValue) {
        _value = newValue;
        std::cout << "MyCppObjectWithCallback value set to: " << _value << std::endl;
        notifyValueChange();
    }

    // 注册一个Dart回调
    void registerCallback(Dart_Port sendPortId) {
        std::lock_guard<std::mutex> lock(_callbacksMutex);
        // 简单起见,这里只存储SendPort ID,不判断重复注册
        // 实际应用中可能需要更复杂的管理,例如为每个回调分配一个ID并返回给Dart
        _callbacks.push_back({sendPortId});
        std::cout << "Registered Dart callback with port ID: " << sendPortId << std::endl;
    }

    // 移除一个Dart回调
    void unregisterCallback(Dart_Port sendPortId) {
        std::lock_guard<std::mutex> lock(_callbacksMutex);
        _callbacks.erase(
            std::remove_if(_callbacks.begin(), _callbacks.end(),
                           [sendPortId](const DartCallbackInfo& info) {
                               return info.sendPortId == sendPortId;
                           }),
            _callbacks.end()
        );
        std::cout << "Unregistered Dart callback with port ID: " << sendPortId << std::endl;
    }

private:
    int _value;
    std::vector<DartCallbackInfo> _callbacks;
    std::mutex _callbacksMutex; // 保护_callbacks的访问

    // 通知所有注册的Dart回调
    void notifyValueChange() {
        std::lock_guard<std::mutex> lock(_callbacksMutex);
        for (const auto& callbackInfo : _callbacks) {
            // 构造Dart_CObject消息
            Dart_CObject message;
            message.type = Dart_CObject_kInt64;
            message.value.as_int64 = _value; // 发送当前值

            // 使用 Dart_PostCObject 发送消息到Dart Isolate
            // Dart_PostCObject 是线程安全的,可以在任何线程调用
            bool posted = Dart_PostCObject(callbackInfo.sendPortId, &message);
            if (!posted) {
                std::cerr << "Warning: Failed to post message to Dart port "
                          << callbackInfo.sendPortId << std::endl;
            } else {
                std::cout << "Posted value " << _value << " to Dart port "
                          << callbackInfo.sendPortId << std::endl;
            }
        }
    }
};

// C风格的API,用于Dart FFI
extern "C" {
    EXPORT_API MyCppObjectWithCallback* my_cpp_object_with_callback_create(int initialValue);
    EXPORT_API void my_cpp_object_with_callback_destroy(MyCppObjectWithCallback* obj);
    EXPORT_API void my_cpp_object_with_callback_increment(MyCppObjectWithCallback* obj);
    EXPORT_API int my_cpp_object_with_callback_get_value(MyCppObjectWithCallback* obj);
    EXPORT_API void my_cpp_object_with_callback_set_value(MyCppObjectWithCallback* obj, int newValue);
    EXPORT_API void my_cpp_object_with_callback_register_callback(MyCppObjectWithCallback* obj, Dart_Port sendPortId);
    EXPORT_API void my_cpp_object_with_callback_unregister_callback(MyCppObjectWithCallback* obj, Dart_Port sendPortId);
}

#endif // MY_CPP_OBJECT_WITH_CALLBACK_H

C++代码 (my_cpp_object_with_callback.cpp)

#include "my_cpp_object_with_callback.h"
#include <algorithm> // For std::remove_if

extern "C" {
    MyCppObjectWithCallback* my_cpp_object_with_callback_create(int initialValue) {
        return new MyCppObjectWithCallback(initialValue);
    }

    void my_cpp_object_with_callback_destroy(MyCppObjectWithCallback* obj) {
        delete obj;
    }

    void my_cpp_object_with_callback_increment(MyCppObjectWithCallback* obj) {
        if (obj) {
            obj->increment();
        }
    }

    int my_cpp_object_with_callback_get_value(MyCppObjectWithCallback* obj) {
        if (obj) {
            return obj->getValue();
        }
        return -1;
    }

    void my_cpp_object_with_callback_set_value(MyCppObjectWithCallback* obj, int newValue) {
        if (obj) {
            obj->setValue(newValue);
        }
    }

    void my_cpp_object_with_callback_register_callback(MyCppObjectWithCallback* obj, Dart_Port sendPortId) {
        if (obj) {
            obj->registerCallback(sendPortId);
        }
    }

    void my_cpp_object_with_callback_unregister_callback(MyCppObjectWithCallback* obj, Dart_Port sendPortId) {
        if (obj) {
            obj->unregisterCallback(sendPortId);
        }
    }
}

重要提示:

  • dart_api.h 文件通常位于Dart SDK的 include 目录下。编译C++代码时,需要配置编译器以包含此目录。
  • Dart_PostCObject 是异步且线程安全的,这意味着你可以在C++的任何线程中调用它,将数据发送回Dart主Isolate的 ReceivePort
  • Dart_CObject 结构体用于封装不同类型的数据。这里我们发送一个 Int64 类型的值。它支持多种类型,包括 kNull, kBool, kInt32, kInt64, kDouble, kString, kArray, kTypedData 等。

3.3 Dart 侧的接收器与封装

在Dart端,我们需要:

  1. 创建 ReceivePort 来接收来自C++的消息。
  2. ReceivePortnativePort 传递给C++。
  3. Dart封装类 DartCppObjectWithCallback 负责管理C++对象的生命周期,并处理回调注册/注销。

Dart代码 (main.dart)

import 'dart:ffi';
import 'dart:io';
import 'dart:isolate'; // 用于ReceivePort
import 'package:ffi/ffi.dart';

// 1. 定义C函数的Dart FFI签名 (新增回调相关函数)
typedef MyCppObjectWithCallbackCreateC = Pointer<Void> Function(Int32 initialValue);
typedef MyCppObjectWithCallbackCreateDart = Pointer<Void> Function(int initialValue);

typedef MyCppObjectWithCallbackDestroyC = Void Function(Pointer<Void> obj);
typedef MyCppObjectWithCallbackDestroyDart = void Function(Pointer<Void> obj);

typedef MyCppObjectWithCallbackIncrementC = Void Function(Pointer<Void> obj);
typedef MyCppObjectWithCallbackIncrementDart = void Function(Pointer<Void> obj);

typedef MyCppObjectWithCallbackGetValueC = Int32 Function(Pointer<Void> obj);
typedef MyCppObjectWithCallbackGetValueDart = int Function(Pointer<Void> obj);

typedef MyCppObjectWithCallbackSetValueC = Void Function(Pointer<Void> obj, Int32 newValue);
typedef MyCppObjectWithCallbackSetValueDart = void Function(Pointer<Void> obj, int newValue);

typedef MyCppObjectWithCallbackRegisterCallbackC = Void Function(Pointer<Void> obj, Int64 sendPortId);
typedef MyCppObjectWithCallbackRegisterCallbackDart = void Function(Pointer<Void> obj, int sendPortId);

typedef MyCppObjectWithCallbackUnregisterCallbackC = Void Function(Pointer<Void> obj, Int64 sendPortId);
typedef MyCppObjectWithCallbackUnregisterCallbackDart = void Function(Pointer<Void> obj, int sendPortId);

// 2. 加载动态库并封装C函数 (更新为带回调的C++类)
class NativeBindingsWithCallback {
  late final DynamicLibrary _nativeLib;

  late final MyCppObjectWithCallbackCreateDart myCppObjectCreate;
  late final MyCppObjectWithCallbackDestroyDart myCppObjectDestroy;
  late final MyCppObjectWithCallbackIncrementDart myCppObjectIncrement;
  late final MyCppObjectWithCallbackGetValueDart myCppObjectGetValue;
  late final MyCppObjectWithCallbackSetValueDart myCppObjectSetValue;
  late final MyCppObjectWithCallbackRegisterCallbackDart myCppObjectRegisterCallback;
  late final MyCppObjectWithCallbackUnregisterCallbackDart myCppObjectUnregisterCallback;

  NativeBindingsWithCallback() {
    if (Platform.isWindows) {
      _nativeLib = DynamicLibrary.open('libmy_cpp_object_with_callback.dll');
    } else if (Platform.isMacOS) {
      _nativeLib = DynamicLibrary.open('libmy_cpp_object_with_callback.dylib');
    } else if (Platform.isLinux) {
      _nativeLib = DynamicLibrary.open('libmy_cpp_object_with_callback.so');
    } else {
      throw UnsupportedError('Unsupported platform');
    }

    myCppObjectCreate = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackCreateC, MyCppObjectWithCallbackCreateDart>('my_cpp_object_with_callback_create');
    myCppObjectDestroy = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackDestroyC, MyCppObjectWithCallbackDestroyDart>('my_cpp_object_with_callback_destroy');
    myCppObjectIncrement = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackIncrementC, MyCppObjectWithCallbackIncrementDart>('my_cpp_object_with_callback_increment');
    myCppObjectGetValue = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackGetValueC, MyCppObjectWithCallbackGetValueDart>('my_cpp_object_with_callback_get_value');
    myCppObjectSetValue = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackSetValueC, MyCppObjectWithCallbackSetValueDart>('my_cpp_object_with_callback_set_value');
    myCppObjectRegisterCallback = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackRegisterCallbackC, MyCppObjectWithCallbackRegisterCallbackDart>('my_cpp_object_with_callback_register_callback');
    myCppObjectUnregisterCallback = _nativeLib
        .lookupFunction<MyCppObjectWithCallbackUnregisterCallbackC, MyCppObjectWithCallbackUnregisterCallbackDart>('my_cpp_object_with_callback_unregister_callback');
  }
}

// 3. Dart封装类,持有C++对象的指针并管理其生命周期,并处理回调
class DartCppObjectWithCallback {
  Pointer<Void> _nativeInstance;
  static final NativeBindingsWithCallback _bindings = NativeBindingsWithCallback();

  // NativeFinalizer 保持不变
  static final NativeFinalizer _finalizer =
      NativeFinalizer(_bindings.myCppObjectDestroy.asFunction<void Function(Pointer<Void>)>());

  // 用于接收C++回调消息的端口
  late final ReceivePort _receivePort;
  // 存储回调函数
  void Function(int value)? onValueChanged;

  DartCppObjectWithCallback(int initialValue)
      : _nativeInstance = _bindings.myCppObjectCreate(initialValue) {
    _finalizer.attach(this, _nativeInstance, detach: this);

    _receivePort = ReceivePort();
    _receivePort.listen((message) {
      if (message is int) { // C++发送的是int64
        print('Dart received callback: Value changed to $message');
        onValueChanged?.call(message);
      } else {
        print('Dart received unknown message type: $message');
      }
    });

    // 将Dart的ReceivePort的native ID注册到C++对象
    _bindings.myCppObjectRegisterCallback(_nativeInstance, _receivePort.sendPort.nativePort);
    print('Registered Dart ReceivePort with native ID: ${_receivePort.sendPort.nativePort}');
  }

  void increment() {
    _bindings.myCppObjectIncrement(_nativeInstance);
  }

  int getValue() {
    return _bindings.myCppObjectGetValue(_nativeInstance);
  }

  void setValue(int newValue) {
    _bindings.myCppObjectSetValue(_nativeInstance, newValue);
  }

  void dispose() {
    if (_nativeInstance != nullptr) {
      // 在销毁C++对象前,先从C++端注销回调
      _bindings.myCppObjectUnregisterCallback(_nativeInstance, _receivePort.sendPort.nativePort);
      _receivePort.close(); // 关闭ReceivePort
      print('ReceivePort closed for native ID: ${_receivePort.sendPort.nativePort}');

      _bindings.myCppObjectDestroy(_nativeInstance);
      _nativeInstance = nullptr;
      _finalizer.detach(this);
      print('DartCppObjectWithCallback explicitly disposed.');
    }
  }

  @override
  String toString() {
    return 'DartCppObjectWithCallback(nativeAddress: ${_nativeInstance.address}, value: ${getValue()})';
  }
}

void main() async {
  print('--- Creating DartCppObjectWithCallback ---');
  DartCppObjectWithCallback obj = DartCppObjectWithCallback(10);

  // 注册Dart回调处理函数
  obj.onValueChanged = (newValue) {
    print('>> Dart callback handler: Current value is $newValue');
  };

  print('Initial value: ${obj.getValue()}');
  obj.increment(); // 触发C++内部回调
  obj.setValue(50); // 触发C++内部回调
  obj.increment(); // 触发C++内部回调

  print('n--- Disposing DartCppObjectWithCallback ---');
  obj.dispose(); // 显式销毁,应注销回调并关闭端口

  // 再次操作已销毁的对象可能会导致问题
  // obj.increment();

  print('n--- Testing GC with DartCppObjectWithCallback ---');
  void createAndLetGoWithCallback() {
    DartCppObjectWithCallback obj2 = DartCppObjectWithCallback(1000);
    obj2.onValueChanged = (newValue) {
      print('>> Dart callback handler for obj2: Current value is $newValue');
    };
    obj2.increment();
    obj2.setValue(2000);
  }

  createAndLetGoWithCallback();
  print('Obj2 should be out of scope. Waiting for GC and finalizer...');
  // 制造垃圾并等待,以便GC运行
  for (int i = 0; i < 100000; i++) {
    List.filled(1000, 0, growable: false);
  }
  await Future.delayed(Duration(milliseconds: 100)); // 给GC一些时间
  print('End of main. C++ destructors and Dart ReceivePort closures for GC'd objects should have been called.');
}

运行结果示例 (可能因为GC时机和异步消息顺序而略有差异)

--- Creating DartCppObjectWithCallback ---
MyCppObjectWithCallback constructed with value: 10
Registered Dart callback with port ID: 123456789 (这是一个示例ID,实际是动态生成的)
Initial value: 10
MyCppObjectWithCallback value incremented to: 11
Posted value 11 to Dart port 123456789
Dart received callback: Value changed to 11
>> Dart callback handler: Current value is 11
MyCppObjectWithCallback value set to: 50
Posted value 50 to Dart port 123456789
Dart received callback: Value changed to 50
>> Dart callback handler: Current value is 50
MyCppObjectWithCallback value incremented to: 51
Posted value 51 to Dart port 123456789
Dart received callback: Value changed to 51
>> Dart callback handler: Current value is 51

--- Disposing DartCppObjectWithCallback ---
Unregistered Dart callback with port ID: 123456789
ReceivePort closed for native ID: 123456789
MyCppObjectWithCallback destructed. Value was: 51
DartCppObjectWithCallback explicitly disposed.

--- Testing GC with DartCppObjectWithCallback ---
MyCppObjectWithCallback constructed with value: 1000
Registered Dart callback with port ID: 987654321 (又是一个示例ID)
MyCppObjectWithCallback value incremented to: 1001
Posted value 1001 to Dart port 987654321
Dart received callback: Value changed to 1001
>> Dart callback handler for obj2: Current value is 1001
MyCppObjectWithCallback value set to: 2000
Posted value 2000 to Dart port 987654321
Dart received callback: Value changed to 2000
>> Dart callback handler for obj2: Current value is 2000
Obj2 should be out of scope. Waiting for GC and finalizer...
End of main. C++ destructors and Dart ReceivePort closures for GC'd objects should have been called.
Unregistered Dart callback with port ID: 987654321
ReceivePort closed for native ID: 987654321
MyCppObjectWithCallback destructed. Value was: 2000

关键点:

  • ReceivePortsendPort.nativePort: Dart侧创建 ReceivePort 来接收消息,其 sendPort.nativePort 是一个 int64 类型的ID,可以安全地传递给C++。C++使用这个ID通过 Dart_PostCObject 发送消息。
  • Dart_CObject: C++端构造 Dart_CObject 来封装需要发送的数据。它支持多种基本类型、字符串、数组等。
  • 异步回调Dart_PostCObject 是异步的。C++发送消息后立即返回,Dart会在事件循环中处理 ReceivePort 收到的消息。
  • 回调生命周期: 当 DartCppObjectWithCallbackdispose 或被GC时,我们必须确保C++端对应的回调也被注销,并且 ReceivePort 被关闭,以避免资源泄漏或向一个已不存在的端口发送消息。
  • 线程安全: C++类内部使用 std::mutex 来保护 _callbacks 向量,因为 registerCallback, unregisterCallback, 和 notifyValueChange 可能在不同的C++线程中被调用(虽然本例中都是在FFI调用线程中,但这是一个好的实践)。Dart_PostCObject 本身是线程安全的。

4. 高级考量与最佳实践

4.1 错误处理

C++与Dart交互时,错误处理至关重要。

  • 返回值: C++函数可以返回错误码(如 int 类型,0表示成功,非0表示错误码)或特殊的错误值(如 -1)。Dart端需要检查这些返回值。
  • 错误信息: C++可以通过FII返回字符串(char*)形式的错误信息。Dart需要负责 mallocfree 这些字符串的内存。
  • 异常: FFI不支持直接传递C++异常到Dart。如果C++代码可能抛出异常,必须在C++的FFI封装层捕获并转换为错误码或错误消息。

4.2 线程安全与并发

  • FFI调用: 默认情况下,Dart FFI 调用是同步的,并在Dart Isolate的主线程上执行。如果C++函数执行时间长,会阻塞Dart UI。
  • 异步 C++ 操作: 如果C++操作是异步的(例如,执行一个耗时任务并完成后回调),那么C++内部应使用自己的线程池。回调到Dart时,必须使用 Dart_PostCObject,因为这是从非Dart线程向Dart Isolate安全发送数据的唯一方式。
  • Dart_EnterIsolate/Dart_ExitIsolate: 如果C++线程需要执行复杂的Dart API操作(例如创建Dart对象、调用Dart方法等),而不仅仅是发送 Dart_CObject 消息,那么该C++线程必须先通过 Dart_EnterIsolate 进入一个Dart Isolate,操作完成后通过 Dart_ExitIsolate 退出。这通常用于更复杂的嵌入场景,而不是简单的FFI回调。

4.3 复杂数据结构传递

  • 结构体: Dart FFI提供了 Struct 类来映射C结构体。可以在Dart中定义一个继承 ffi.Struct 的类,并使用 Pointer<MyStruct> 进行操作。
  • 数组和列表: 通常通过 Pointer<T> 和长度参数来传递。Dart TypedData (如 Uint8List, Int32List) 可以直接与 Pointer<Uint8>, Pointer<Int32> 等进行内存共享(asTypedList)。
  • 字符串: C++通常使用 char*。Dart可以使用 toNativeUtf8 将Dart String 转换为C风格的UTF-8字符串指针(需要 calloc),并使用 toDartStringchar* 转换为Dart String。注意内存管理,C++返回的 char* 必须在Dart端 free
  • Map 和其他复杂对象: 通常需要手动序列化/反序列化。在 Dart_CObject 消息中,可以使用 Dart_CObject_kArray 封装键值对,或者将其序列化为JSON字符串传递。

4.4 内存对齐

Dart ffi.Struct 会自动处理内存对齐,但如果C++结构体有特殊的对齐要求,可以使用 @Packed(N) 注解来强制Dart Struct 的对齐方式。

4.5 C++ 智能指针与 FFI

如果C++类广泛使用智能指针(如 std::shared_ptr, std::unique_ptr)进行内存管理,那么FFI封装层需要特别小心。

  • std::unique_ptr: 通常只在C++内部管理,对外暴露裸指针,并在FFI destroy 函数中 releasedelete
  • std::shared_ptr: 更复杂。如果Dart也需要共享所有权,C++ FFI层可能需要返回一个 shared_ptr 的引用计数器增加后的裸指针,并在Dart NativeFinalizer 中调用一个C++函数来减少引用计数。但这种模式增加了复杂性,通常建议保持所有权在C++端,Dart只持有不透明指针。

4.6 工具辅助

  • package:ffigen: 这是一个强大的Dart工具,可以根据C/C++头文件自动生成Dart FFI绑定代码,大大减少手动编写的重复工作和出错几率。对于大型C/C++库的集成,ffigen 是一个非常推荐的工具。

5. 总结

本文详细阐述了如何在Dart应用中实现与C++对象的双向绑定。从Dart FFI的基础用法开始,我们首先解决了Dart引用C++对象时的内存管理挑战,通过 NativeFinalizer 实现了C++资源与Dart对象生命周期的同步。随后,我们深入探讨了C++引用Dart对象并进行回调的复杂机制,利用 ReceivePortDart_PostCObject 实现了C++到Dart的异步消息传递。

成功的双向绑定需要对两个运行时环境的内存模型和并发机制有深刻理解。通过精心设计的C++ FFI封装层、Dart的 NativeFinalizerReceivePort 机制,我们不仅能扩展Dart应用的能力,还能确保资源安全和应用的稳定性。在实际项目中,结合 ffigen 等工具,可以进一步提高开发效率和代码质量。

发表回复

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