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

本文关键字:对齐 记录 结构 Delphi | 更新日期: 2023-09-27 18:29:56

所以我有一个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和4个字节

0x00,0x00,0x0,0x002字节用于构建,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. 我有许多记录类型,其中一些类型的成员是其他记录的动态数组。我宁愿想出一个健壮的解决方案来将这些字节数组转换为结构或对象

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

编译器通常将对齐用作优化。实际上,每个结构都填充了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;

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

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);

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

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

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

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

在您的情况下,填充位于8字节双字节之前,并且位于记录的末尾。如果末尾的这个填充不包括在内,那么在记录的任何数组中,8字节的双精度都会被错误对齐。

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

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

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

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