在 C# 中正确封送 C++ 结构

本文关键字:C++ 结构 | 更新日期: 2023-09-27 18:35:53

我C++有这个结构,我需要转换为 C#,所以我可以从 byte[] 创建这个结构。

struct  TRANS_RECORD_DATA {
    int size, code;
    int     ThrowItemCount;
    int     ItemCount;
    int     ItemSubStart;
    int     DataSize;
    BYTE    Data[sizeof(sRECORD_ITEM) * 200]; // sizeof(sRECORD_ITEM) = 548
};

C# 版本:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
 struct TRANS_RECORD_DATA {
    public int size, code;
        public int ThrowItemCount;
        public int ItemCount;
        public int ItemSubStart;
        public int DataSize;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 548*200)]
        public byte[] Data;
    };

正在使用这个通用函数来为我提供字节数组中的结构:

T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T stuff = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (T));
    handle.Free();
    return stuff;
}

但它给了我:

访问冲突异常。

我相信我知道为什么,但我不知道如何解决它。我需要映射到结构中的byte[]并不总是具有大小为 548*200 的 Data 成员。此数字是最大值。但似乎我使用的通用方法总是尝试使用该数据始终为 548*200 创建结构,然后它显然会抛出 AccessViolation ,因为要映射的数据已经结束。

例如,此代码:

var bytes = new byte[26];
var structure = ByteArrayToStructure<TRANS_RECORD_DATA>(bytes);

应该返回一个TRANS_RECORD_DATA,其中包含所有值为 0 的 int 成员,最后该byte[] Data将只有剩余的两个字节。(26 - 24 = 2)。但它似乎一直试图创建完整的 548*200 字节[],然后导致访问冲突。

有没有办法解决这个问题?

在 C# 中正确封送 C++ 结构

因此,当您解释它时,无论数据数组的 C 定义长度为 200*548 字节,它实际上并没有完全由您调用的外部非托管代码分配或填充。

这样,唯一的解决方法是不在 C# 结构定义中定义 Data:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TRANS_RECORD_DATA {
    public int size, code;
    public int ThrowItemCount;
    public int ItemCount;
    public int ItemSubStart;
    public int DataSize;
};

并在读取 DataSize 后将字节数组的其余部分重新解释为该数据结构。

您仍然可以使用对 Marshal.PtrToStructure 的一系列调用:

GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
TRANS_RECORD_DATA  stuff = (TRANS_RECORD_DATA) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (TRANS_RECORD_DATA));
var items = new List<sRECORD_ITEM>(stuff.ItemCount);
for (int i = 0; i < stuff.ItemCount; ++i)
{
    var ptr = handle.AddrOfPinnedObject().Add(i*548)
    sRECORD_ITEM item = (sRECORD_ITEM)Marshal.PtrToStructure(ptr,typeof(sRECORD_ITEM));
}

我很确定这应该可以解决问题。