如何使用 C# 读取 HDF.PInvoke 中包含数组(固定大小)的复合数据集

问题描述 投票:0回答:1

我有一个 HDF5 数据集,我正在尝试使用 C# 和 HDF.PInvoke(基本上是 C API 的包装器)读取它。

它包含一个包含复合数据类型数组的部分。数据类型包含一个有 9 个元素的数组。当我运行代码时,当我尝试创建

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;
  }
}
c# marshalling hdf5
1个回答
0
投票

问题中提到的上述代码的问题是在

GCHandle.Alloc(...)
方法中抛出异常,因为testData数组包含不可blittable的组件。 解决方案是创建一个字节缓冲区数组并将其用于
GCHandle
。然后可以使用一个简单的
BinaryReader
循环将正确的数据元素分配给正确的数据类型:-

所以首先我们将上面

H5D.Read
部分的代码替换为:-

... 
    int numEntries = (int)dimsFam[0];
    hid_t datatype = H5D.get_type(dataset);
    byte[] buffer = new byte[numEntries * 76];// 76 is the size of TestData
    GCHandle pinnedArray = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    H5D.read(dataset, memtype, H5S.ALL, H5S.ALL, H5P.DEFAULT, pinnedArray.AddrOfPinnedObject());
    pinnedArray.Free();
    testData = ReadObjectsFromBuffer(buffer, numEntries);
    H5T.close(datatype);
...

对于 TestData 结构的特定情况,我们有:-

private static TestData[] ReadObjectsFromBuffer(byte[] buffer, int num)
{
   TestData[] res = new TestData[num];
   using (MemoryStream ms = new MemoryStream(buffer))            
     using(BinaryReader br = new BinaryReader(ms)) 
     {
       for (int i = 0; i < num; i++)
       {
          res[i] = new TestData();
          for (int j = 0; j < res[i].ArrayVariable.Length; j++)            
             res[i].ArrayVariable[j] = br.ReadDouble();            
          res[i].FloatVariable = br.ReadSingle();
       }
     }            
     return res;
}

如果有更好的解决方案,不涉及显式循环数据,我有兴趣了解它。

© www.soinside.com 2019 - 2024. All rights reserved.