我有一个 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;
}
}
问题中提到的上述代码的问题是在
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;
}
如果有更好的解决方案,不涉及显式循环数据,我有兴趣了解它。