1
votes

So I have a Delphi application that is taking records of various types, throwing them on a memorystream via stream.Write(record, sizeof(record)) and sending them over a named pipe.

Take this Delphi record :

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

When this is sent over the named pipe, it comes out like this in a byte[] array :

Length: 22 Bytes

0x06, 0x00, 0x00, 0x00, 4 bytes for array

0x00, 0x00, 0x00, 0x00, 2 bytes for build, 2 bytes for alignment?

0x15, 0xA3, 0x86, 0x3F, 8 bytes for double

0xBC, 0x44, 0xE4, 0x40,

0xA3, 0x02, 0x00, 0x00, 2 bytes for SVNChangeSet, 2 bytes alignment?

0x00, 0x00, 2 bytes for something else?

Alignment Questions

  1. I believe this is called alignment in 4-byte boundries, correct?
  2. What are the last two bytes for?

Now I'm trying (unsuccessfully) to marshal this into a 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# Questions

  1. This simply does not work, and I can't really figure out how to account for the alignment. I've tried explicit offsets but I'm coming up short.
  2. I have many record types, some with members that are dynamic arrays of other records. I'd rather come up with a robust solution to converting these byte arrays into structures or objects.
2
By the way, array[0..4] of Byte contains 5 bytes, not 4. What is SizeOf(record) in Delphi? I guess it's 20, not 22.nullptr
SizeOf(TAboutData) in Delphi 2007 and XE4 both give 24, which makes sense with padding.Ken White

2 Answers

3
votes

Alignment is normally used by the compiler as an optimization. Actually every structure is padded to a multiple of 4 (or 8 I don't remember exactly).

Update: What I state above about alignment is not accurate. Have a read of David's answer for details on how aligned records are treated by the compiler. The Wikipedia article contains a reasonable overview: http://en.wikipedia.org/wiki/Data_structure_alignment

Anyway, you can use the Pack parameter to specify the alignment. A Pack value of 1 returns the exact size of the structure.

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

About arrays, it's correct to use:

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

Just take care when you convert from structure to byte array that the array must match the declared size. Before converting, you should use Array.Resize if the content is shorter.

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

About marshaling I use this method from byte[] to structure:

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

And this method does the opposite:

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

Also, please use the following code to check the size calculated by the framework:

int objectSize = Marshal.SizeOf(structure);

Finally I found this nice article about marshalling.

2
votes

I'm going to assume that your Delphi compiler is operating in the default mode and so is aligning records. I also assume that your Pascal code contains a simple typo where your array has 5 elements instead of 4.

The alignment of an aligned record is determined by the member with the largest alignment. The largest member is the 8 byte double. So the record has alignment 8. Its size is an exact multiple of its alignment.

Each individual field is aligned to match the field's alignment. The byte array has alignment 1 and can appear anywhere. The 2 byte integers must appear on 2 byte boundaries, and so on. Records may have padding at the end to ensure that arrays of the record will also be aligned. So the record has size that is a multiple of 8. Your record has size 24.

In your case, the padding is before the 8 byte double, and at the end of the record. If this padding at the end was not included, the 8 byte double would be mis-aligned in any arrays of your record.

There's no need to make any changes in your Delphi record and C# struct declarations. They are already declared correctly, and match each other perfectly.

You can use the SizeOf in Delphi and Marshal.SizeOf in C# to check the size of your structures. You will find that they match with the code in the question.

I cannot comment on how your code failed because you did not describe that failure. My main point is that any failure is not because of a mis-match between your structures. There's no rational explanation for the source of the number 22. I'd look to where that number comes from.

Finally, the answer that you accepted proposes using packed structures. There's no need to do that, I see no reason to do so, and it does not explain your problems.