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

如何通过FFI在Dart与C之间规范共享TypedData?

问题

我有一个TypedData实例中的原始数据,希望通过FFI调用C库处理,处理结果同样是TypedData。具体场景如下:

  • 我们有一个以双精度数组存储的动态热力图,每帧都会更新
  • 需要基于顶点生成该热力图的Voronoi表示,理想情况是直接拿到可用于Vertices.raw的Float32List(位置数据)和Int32List(颜色数据),且这些数组能复用,不用每帧重新实例化

我不确定怎么用Dart FFI正确实现这个需求。理想状态是在Dart中持有这些类型化数据:

// 初始化
final heatmap = Float64List(500 * 500);
final outPositions = Float32List(maxVertices * 2);
final outColors = Int32List(maxVertices);

之后调用C函数,通过引用传入输入和输出数据:

// 后续每帧调用
ffiCall(heatmap, outPositions, outColors);

// 使用处理后的输出数据

目前知道.address属性,但它只适用于leaf native调用,我不确定是否要把FFI函数设为leaf调用。另外用Struct可以处理静态大小的数组,比如:

final class MyStruct extends Struct {
  @Array(100 * 100)
  external Array<Uint8> inlineArray;
}

但很多场景下数组大小只有运行时才能确定。本质上,我希望在Dart中构造对应如下C结构体的对象,然后以引用形式传给C代码:

// 用于指定要在C中处理的Dart数据的结构体
typedef struct
{
  int width;
  int height;
  double* data;
} my_struct;

如果没法实现上述需求,我也希望尽可能减少Dart和C之间的大数据拷贝。之前找到过类似问题,但提问者能接受数毫秒的拷贝耗时,这只适合偶尔执行的操作,满足不了每帧运行的要求。

请问这个需求可行吗?这类场景的规范实现方式是什么?


解决方案

这个需求完全可行,是Dart FFI处理高性能跨语言数据交互的典型场景,以下是规范实现步骤:

1. 定义对应C结构体的Dart Struct

针对你需要的C结构体,在Dart中定义对应的Struct子类,通过指针字段承载运行时确定大小的数组:

import 'dart:ffi' as ffi;

final class HeatmapInput extends ffi.Struct {
  @ffi.Int32()
  external int width;

  @ffi.Int32()
  external int height;

  // 对应C中的double*,指向Dart Float64List的原始内存
  external ffi.Pointer<ffi.Double> data;
}

// 为输出数据定义结构体,也可直接传递指针+最大顶点数
final class VoronoiOutput extends ffi.Struct {
  @ffi.Int32()
  external int maxVertices;

  // 对应Float32List的原始内存,存储(x,y)坐标
  external ffi.Pointer<ffi.Float> positions;

  // 对应Int32List的原始内存,存储颜色值
  external ffi.Pointer<ffi.Int32> colors;
}

2. 绑定C函数

假设你的C函数签名如下:

void generate_voronoi(const my_struct* input, const VoronoiOutput* output, int* out_vertex_count);

在Dart中绑定这个函数时直接使用指针类型:

typedef GenerateVoronoiC = ffi.Void Function(
  ffi.Pointer<HeatmapInput> input,
  ffi.Pointer<VoronoiOutput> output,
  ffi.Pointer<ffi.Int32> outVertexCount,
);

typedef GenerateVoronoiDart = void Function(
  ffi.Pointer<HeatmapInput> input,
  ffi.Pointer<VoronoiOutput> output,
  ffi.Pointer<ffi.Int32> outVertexCount,
);

// 加载动态库并获取函数指针
final dylib = ffi.DynamicLibrary.open('your_library.so'); // 替换为对应平台的库文件
final generateVoronoi = dylib.lookupFunction<GenerateVoronoiC, GenerateVoronoiDart>('generate_voronoi');

3. 初始化TypedData并传递给C

在Dart中初始化数组后,通过.dataAsPointer(等价于.address.cast<T>())获取内存指针,填充到Struct实例中再传递给C函数:

// 初始化阶段(仅执行一次)
const heatmapWidth = 500;
const heatmapHeight = 500;
final heatmap = Float64List(heatmapWidth * heatmapHeight);
const maxVertices = 10000;
final outPositions = Float32List(maxVertices * 2);
final outColors = Int32List(maxVertices);

// 分配并填充输入结构体
final input = ffi.calloc<HeatmapInput>();
input.ref.width = heatmapWidth;
input.ref.height = heatmapHeight;
input.ref.data = heatmap.dataAsPointer;

// 分配并填充输出结构体
final output = ffi.calloc<VoronoiOutput>();
output.ref.maxVertices = maxVertices;
output.ref.positions = outPositions.dataAsPointer;
output.ref.colors = outColors.dataAsPointer;

// 用于接收实际生成的顶点数
final outVertexCount = ffi.calloc<ffi.Int32>();

// 每帧调用逻辑
void updateFrame() {
  // 先更新heatmap的动态数据...

  // 调用C函数处理,直接操作Dart数组的原始内存
  generateVoronoi(input, output, outVertexCount);

  // 获取实际顶点数,用于后续渲染
  final vertexCount = outVertexCount.value;

  // 使用处理后的输出数据,比如传给Vertices.raw
  // Vertices.raw(VertexMode.triangles, outPositions.sublist(0, vertexCount * 2), colors: outColors.sublist(0, vertexCount));
}

// 程序结束时释放分配的Struct内存
void dispose() {
  ffi.calloc.free(input);
  ffi.calloc.free(output);
  ffi.calloc.free(outVertexCount);
}

4. 关于leaf调用的说明

  • .dataAsPointer(或.address)可以安全用于非leaf调用,只要保证C函数执行期间,Dart的TypedData数组不会被GC回收。由于你是复用数组,只要持有数组的对象存活,就不会有问题。
  • 如果你的C函数是纯计算型(不调用Dart回调、不阻塞、不分配内存),可以在绑定函数时加@ffi.Leaf()注解标记为leaf调用,能获得轻微性能提升,但不是必须的。

关键注意事项

  • 必须传递数组的实际大小(比如maxVertices)给C函数,避免C代码越界访问Dart数组内存。
  • Dart的TypedData内存由GC管理,不要在C代码中长期持有这些指针,仅在函数调用期间使用。
  • 你的场景完全不需要数据拷贝,C代码直接操作Dart侧数组的原始内存,性能可以达到最优。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:35:59