1:1 C#结构与流式Delphi记录的对齐可能吗?

时间:2013-08-31 20:58:44

标签: c# delphi pinvoke

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

拿这个Delphi记录:

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,2个字节用于SVNChangeSet,2个字节对​​齐?

     

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

对齐问题

  1. 我相信这称为4字节边界对齐,对吗?
  2. 最后两个字节是什么?
  3. 现在我正在尝试(不成功)将其编组为C#struct。

        [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. 我有很多记录类型,其中一些成员是其他记录的动态数组。我宁愿想出一个强大的解决方案来将这些字节数组转换为结构或对象。

2 个答案:

答案 0 :(得分: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);

最后我发现这个很好article关于编组。

答案 1 :(得分:2)

我将假设您的Delphi编译器在默认模式下运行,因此正在调整记录。我还假设您的Pascal代码包含一个简单的拼写错误,其中您的数组有5个元素而不是4个。

对齐记录的对齐方式由具有最大对齐的成员确定。最大的成员是8字节双。因此记录具有对齐8.其大小是其对齐的精确倍数。

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

在您的情况下,填充在8字节双精度之前,并在记录的末尾。如果未包含末尾的填充,则8字节双精度值将在您的记录的任何数组中错位。

您无需对Delphi记录和C#struct声明进行任何更改。它们已被正确声明,并且完美匹配。

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

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

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