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

如何用C#通过HDF.PInvoke读取含固定数组的复合HDF5数据集

问题:使用HDF.PInvoke读取含数组的复合类型HDF5数据集时GCHandle报错

我正在尝试用C#和HDF.PInvoke(HDF5 C API的封装)读取一个HDF5数据集,这个数据集里是复合数据类型的数组,其中复合类型包含一个9元素的double数组。运行代码时,创建GCHandle时抛出如下错误:

System.ArgumentException
HResult=0x80070057
Message=Object contains non-primitive or non-blittable data. (Parameter 'value')
Source=System.Private.CoreLib

代码在处理不含数组的复合类型时正常运行,我怀疑问题出在double[9]数组的固定大小需求和通过GCHandle传递数组的方式不匹配(可能需要用指针?),请问我哪里错了?

以下是简化后的代码:

[StructLayout(LayoutKind.Explicit, Size = 76, Pack = 1)]
public struct TestData
{
  [FieldOffset(0)]
  public double[] ArrayVariable = new double[9];
  [FieldOffset(72)]
  public Single FloatVariable = 0;                
   
  public TestData(){}
}
    
public static TestData[] LoadTestData(hid_t pg, string item)
{
  hid_t memtype = H5T.create(H5T.class_t.COMPOUND, (ssize_t)76);
  hsize_t[] array_dims = { 9 };
  hid_t compoundTypeID= H5T.array_create(H5T.NATIVE_DOUBLE, 1, array_dims);
   
  H5T.insert(memtype, "ArrayVariable", (ssize_t)0, compoundTypeID);//+72
  H5T.insert(memtype, "FloatVariable", (ssize_t)72, H5T.NATIVE_FLOAT);//+4
    
  TestData[] testData = null;
  if (TryOpenDataSet(pg, item, out hid_t dataset))
  {                
   hid_t dataspace = H5D.get_space(dataset);
   int rank = H5S.get_simple_extent_ndims(dataspace);
   if (rank == 1)
   {
     hsize_t[] dims = new hsize_t[1];
     hsize_t[] maxdims = new hsize_t[1];
     int tst = H5S.get_simple_extent_dims(dataspace, dims, maxdims);
     if (tst != 1)
     {
       throw new HDF5Exception("unexpected dimension number in TestData Array loading");
     }
     int numEntries = (int)dims[0];
     testData = new TestData[numEntries];   
     hid_t datatype = H5D.get_type(dataset);

     GCHandle pinnedArray = GCHandle.Alloc(testData, GCHandleType.Pinned);//****Error here****

     H5D.read(dataset, memtype, H5S.ALL, H5S.ALL,  H5P.DEFAULT, pinnedArray.AddrOfPinnedObject());
     pinnedArray.Free();
                           
     H5T.close(datatype);
   }
   H5D.close(dataset);
 }
 return testData;
}

public static bool TryOpenDataSet(hid_t loc_id, string title, out hid_t datasetID)
{
  try
  {
    datasetID = H5D.open(loc_id, title);
    return !(datasetID < 0);
  }
  catch (Exception)
  {
    datasetID = -1;
    return false;
  }
}

问题原因与解决方法

核心原因

你的TestData结构体里的double[] ArrayVariable是引用类型,而GCHandle.Alloc的Pinned类型要求对象是blittable类型(即托管内存和非托管内存二进制表示完全一致的类型)。因为结构体包含引用类型成员,整个结构体变成非blittable,无法被直接固定,从而触发报错。

解决步骤

1. 将结构体中的数组改为固定大小缓冲区(Fixed Buffer)

使用C#的fixed关键字声明固定大小的缓冲区,这样数组会直接嵌入结构体的内存中,而非作为引用存在。修改后的结构体需要开启不安全代码支持:

[StructLayout(LayoutKind.Explicit, Size = 76, Pack = 1)]
public unsafe struct TestData
{
    [FieldOffset(0)]
    public fixed double ArrayVariable[9]; // 固定大小缓冲区,直接嵌入结构体内存
    [FieldOffset(72)]
    public Single FloatVariable;               

    public TestData() {}
}

注意:需要在项目属性的"生成"选项卡中勾选"允许不安全代码"。

2. 验证内存类型定义

你原有的HDF5内存类型定义是正确的:H5T.array_create创建的9元素double数组类型,和结构体里的fixed buffer内存布局完全匹配,无需修改这部分代码。

3. 访问固定缓冲区元素

读取数据后,需要通过指针操作访问fixed buffer中的元素:

// 示例:访问第一个TestData实例的ArrayVariable第0个元素
unsafe
{
    fixed (TestData* dataPtr = testData)
    {
        double firstValue = dataPtr[0].ArrayVariable[0];
    }
}

4. 无unsafe代码的替代方案(不推荐)

如果不想使用unsafe代码,可以将9元素数组拆分为9个独立的double字段:

[StructLayout(LayoutKind.Explicit, Size = 76, Pack = 1)]
public struct TestData
{
    [FieldOffset(0)] public double ArrayVar0;
    [FieldOffset(8)] public double ArrayVar1;
    [FieldOffset(16)] public double ArrayVar2;
    [FieldOffset(24)] public double ArrayVar3;
    [FieldOffset(32)] public double ArrayVar4;
    [FieldOffset(40)] public double ArrayVar5;
    [FieldOffset(48)] public double ArrayVar6;
    [FieldOffset(56)] public double ArrayVar7;
    [FieldOffset(64)] public double ArrayVar8;
    [FieldOffset(72)] public Single FloatVariable;               
}

同时需要修改HDF5内存类型定义,逐个插入这些字段。这种方式代码冗余,仅作为备选方案。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:47:23