You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在dart:ffi中正确创建并绑定C函数指针MagickProgressMonitor?

Dart FFI绑定C函数指针问题解答

问题描述

我希望在Dart中绑定以下C函数,该函数接收名为progress_monitor的函数指针:

FFI_PLUGIN_EXPORT void* magickSetProgressMonitor(..., const void* progress_monitor,...);

其中progress_monitor是如下类型的函数指针:

typedef bool(*MagickProgressMonitor)(const char *,const long long,
    const long long,void *);

我在Dart中尝试创建函数指针的代码如下:

typedef MagickProgressMonitor = bool Function(String text, int offset, int size, List<int>? clientData);

extension _MagickProgressMonitorExtension on MagickProgressMonitor {
  /// Creates a native function pointer from this method.
  Pointer<Void> toNativeFunctionPointer() {
    return Pointer.fromFunction<Bool Function(Pointer<Char>, LongLong, LongLong, Pointer<Void>)>(this, false).cast();
  }
}

但出现如下错误:

The type 'bool Function(String, int, int, List<int>?)' must be a subtype of 'Bool Function(Pointer<Char>, LongLong, LongLong, Pointer<Void>)' for 'fromFunction'.

我有两个问题:

  • 使用fromFunction<???>时应指定什么类型以消除错误?
  • C函数的最后一个参数是void*指针(可指向任意类型),我当前尝试用Dart的List<int>和C的unsigned char数组表示,但似乎不正确,有没有更正确的处理方式?

更新: 我计划通过如下方式暴露API方法:

void magickSetProgressMonitor(MagickProgressMonitor progressMonitor, [List<int>? clientData]) {
  final Pointer<UnsignedChar> clientDataPtr = clientData?.toUnsignedCharArray() ?? nullptr;
  final Pointer<Void> progressMonitorPtr = progressMonitor.toNativeFunctionPointer();
  Pointer<Void> oldMonitorPtr =
      _bindings.magickSetProgressMonitor(...,progressMonitorPtr,...);
  ...
}

问题解答

1. 修正fromFunction的类型参数

错误根源是Dart侧的函数签名与fromFunction指定的C函数指针签名不兼容。需要拆分函数类型定义,先做原生兼容的签名,再封装成友好的Dart接口:

// 定义与C侧完全匹配的原生函数指针类型
typedef _NativeMagickProgressMonitor = Bool Function(
  Pointer<Char> text,
  LongLong offset,
  LongLong size,
  Pointer<Void> clientData,
);

// 定义对外暴露的Dart友好函数类型
typedef MagickProgressMonitor = bool Function(
  String text,
  int offset,
  int size,
  Pointer<Void> clientData,
);

extension _MagickProgressMonitorExtension on MagickProgressMonitor {
  Pointer<Void> toNativeFunctionPointer() {
    // 适配原生签名的回调逻辑
    _NativeMagickProgressMonitor nativeCallback = (
      Pointer<Char> textPtr,
      LongLong offset,
      LongLong size,
      Pointer<Void> clientData,
    ) {
      // 将C的char*转换为Dart String
      String text = textPtr.toDartString();
      // 调用用户传入的Dart函数,自动处理数值类型转换
      return this(text, offset.toInt(), size.toInt(), clientData) ? Bool(true) : Bool(false);
    };

    // fromFunction的类型参数指定为_NativeMagickProgressMonitor
    return Pointer.fromFunction<_NativeMagickProgressMonitor>(nativeCallback, Bool(false)).cast();
  }
}

这样nativeCallback的签名与_NativeMagickProgressMonitor完全一致,就能通过类型检查。

2. 正确处理void*类型的clientData

C的void*是通用指针,不能直接映射为Dart的List<int>,需根据数据流向分场景处理:

场景1:Dart侧传递数据给C回调

如果是Dart要把自定义数据传给C侧回调,需手动管理原生内存:

void magickSetProgressMonitor(MagickProgressMonitor progressMonitor, [dynamic? clientData]) {
  Pointer<Void> clientDataPtr = nullptr;

  // 处理List<int>类型的客户端数据
  if (clientData is List<int>) {
    // 分配对应长度的原生unsigned char内存
    clientDataPtr = malloc.allocate<UnsignedChar>(clientData.length);
    // 把Dart List的数据拷贝到原生指针
    for (int i = 0; i < clientData.length; i++) {
      clientDataPtr.elementAt(i).value = clientData[i];
    }
  }
  // 处理整数类型的客户端数据
  else if (clientData is int) {
    clientDataPtr = Pointer.fromAddress(clientData);
  }

  final Pointer<Void> progressMonitorPtr = progressMonitor.toNativeFunctionPointer();
  Pointer<Void> oldMonitorPtr = _bindings.magickSetProgressMonitor(..., progressMonitorPtr, clientDataPtr, ...);
  
  // 注意:分配的原生内存需在合适时机释放,避免内存泄漏
  // malloc.free(clientDataPtr);
}

场景2:回调中处理C侧传入的void*

如果是C侧主动传递数据到Dart回调,需把Pointer<Void>转换成对应类型的指针再读取:

// 示例用户自定义的回调函数
bool myProgressCallback(String text, int offset, int size, Pointer<Void> clientData) {
  if (!clientData.isNull) {
    // 将void*转换为unsigned char数组指针
    Pointer<UnsignedChar> dataPtr = clientData.cast<UnsignedChar>();
    // 读取数据(需提前知道数组长度)
    int dataLength = 10; // 示例长度,实际需根据业务逻辑获取
    List<int> dartData = List.generate(dataLength, (i) => dataPtr.elementAt(i).value);
    // 处理dartData...
  }
  return true;
}

内容的提问来源于stack exchange,提问作者Haidar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 00:45:43