1

所以我有一个 Delphi 应用程序,它正在获取各种类型的记录,将它们扔到内存流中,stream.Write(record, sizeof(record))并通过命名管道发送它们。

拿这个德尔福记录:

Type TAboutData = record
  Version : array[0..4] of Byte;
  Build : Word;
  BuildDate : TDateTime;
  SVNChangeset : Word;
end;

当它通过命名管道发送时,它在 byte[] 数组中如下所示:

长度:22 字节

0x06, 0x00, 0x00, 0x00, 4 字节的数组

0x00, 0x00, 0x00, 0x00, 2 个字节用于构建,2 个字节用于对齐?

0x15, 0xA3, 0x86, 0x3F, 8 字节为双精度

0xBC、0x44、0xE4、0x40、

0xA3, 0x02, 0x00, 0x00, SVNChangeSet 2 个字节,2 个字节对齐?

0x00, 0x00, 2 个字节用于别的什么?

对齐问题

  1. 我相信这被称为 4 字节边界对齐,对吗?
  2. 最后两个字节是干什么用的?

现在我正在尝试(不成功)将其编组为 C# 结构。

    [StructLayout(LayoutKind.Sequential)]
    struct TAboutInfo
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public byte[] Version;
        public ushort Build;
        public double BuildDate;
        public ushort SVNChangeSet;
    }    

    IntPtr ptr = Marshal.AllocHGlobal(bytebuffer.Length);
    Marshal.Copy(ptr, bytebuffer, 0, bytebuffer.Length);
    TAboutInfo ta = (TAboutInfo)Marshal.PtrToStructure(ptr, typeof(TAboutInfo));
    Marshal.FreeHGlobal(ptr);

C# 问题

  1. 这根本行不通,我真的不知道如何解释对齐。我已经尝试过明确的偏移量,但我做不到。
  2. 我有许多记录类型,其中一些成员是其他记录的动态数组。我宁愿想出一个强大的解决方案来将这些字节数组转换为结构或对象。
4

2 回答 2

3

对齐通常被编译器用作优化。实际上,每个结构都被填充为 4 的倍数(或者 8,我记不清了)。

更新:我上面关于对齐的陈述是不准确的。有关编译器如何处理对齐记录的详细信息,请阅读 David 的答案。维基百科文章包含一个合理的概述:http ://en.wikipedia.org/wiki/Data_structure_alignment

无论如何,您可以使用 Pack 参数来指定对齐方式。Pack 值为 1 会返回结构的确切大小。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]

关于数组,正确使用:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public char[] someCharArray;

当您从结构转换为字节数组时,请注意数组必须与声明的大小匹配。在转换之前,如果内容较短,则应使用Array.Resize 。

struct.someCharArray = "Hello".ToCharArray();
Array.Resize<char>(ref struct.someCharArray, 20);

关于编组,我使用从 byte[] 到结构的这种方法:

    public static T ParseStructure<T>(byte[] array, int offset) where T : struct
    {
        if (array == null) throw new ArgumentNullException("array", "Input parameter cannot be null");

        if (array.Length - offset < Marshal.SizeOf(typeof(T)))
            Array.Resize<byte>(ref array, Marshal.SizeOf(typeof(T)) + offset);

        int arraySize = array.Length - offset;
        T returnItem;

        // Allocate some unmanaged memory.
        IntPtr buffer = Marshal.AllocHGlobal(arraySize);

        // Copy the read byte array (byte[]) into the unmanaged memory block.
        Marshal.Copy(array, offset, buffer, arraySize);

        // Marshal the unmanaged memory block to a structure.
        returnItem = (T)Marshal.PtrToStructure(buffer, typeof(T));

        // Free the unmanaged memory block.
        Marshal.FreeHGlobal(buffer);

        return returnItem;
 }

而这种方法恰恰相反:

    public static byte[] StructureToArray<T>(T structure) where T : struct
    {
        int objectSize = Marshal.SizeOf(structure);
        byte[] result = new byte[objectSize];
        IntPtr buffer = Marshal.AllocHGlobal(objectSize);

        object dataStructure = (object)structure;

        Marshal.StructureToPtr(dataStructure, buffer, true);

        Marshal.Copy(buffer, result, 0, objectSize);
        Marshal.FreeHGlobal(buffer);
        return result;
    }

另外,请使用以下代码检查框架计算的大小:

int objectSize = Marshal.SizeOf(structure);

最后我找到了这篇关于编组的好文章。

于 2013-08-31T21:06:46.530 回答
2

我将假设您的 Delphi 编译器在默认模式下运行,对齐记录也是如此。我还假设您的 Pascal 代码包含一个简单的错字,其中您的数组有 5 个元素而不是 4 个。

对齐记录的对齐方式由具有最大对齐方式的成员确定。最大的成员是 8 字节双精度。所以记录的对齐方式为 8。它的大小是对齐方式的精确倍数。

每个单独的字段都对齐以匹配字段的对齐方式。字节数组有对齐 1 并且可以出现在任何地方。2 字节整数必须出现在 2 字节边界上,依此类推。记录的末尾可能有填充,以确保记录的数组也将对齐。所以记录的大小是 8 的倍数。您的记录大小为 24。

在您的情况下,填充在 8 字节双精度之前,并且在记录的末尾。如果最后的填充不包括在内,则 8 字节双精度将在您的记录的任何数组中未对齐。

无需对 Delphi 记录和 C# 结构声明进行任何更改。它们已经正确声明,并且彼此完美匹配。

您可以使用 Delphi 中的 SizeOf 和 C# 中的 Marshal.SizeOf 来检查结构的大小。您会发现它们与问题中的代码匹配。

我无法评论您的代码是如何失败的,因为您没有描述该失败。我的主要观点是,任何失败都不是因为你的结构之间的不匹配。22这个数字的来源没有合理的解释。我想看看这个数字是从哪里来的。

最后,您接受的答案建议使用打包结构。没有必要这样做,我认为没有理由这样做,也不能解释你的问题。

于 2013-09-01T06:38:22.003 回答