Възможно ли е подравняване 1:1 на C# структури с поточно предавани записи на Delphi?

Така че имам приложение на 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. За какво са последните два байта?

Сега се опитвам (неуспешно) да маршалирам това в 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. Имам много типове записи, някои с членове, които са динамични масиви от други записи. Предпочитам да измисля стабилно решение за преобразуване на тези масиви от байтове в структури или обекти.

person Danny Rodriguez    schedule 31.08.2013    source източник
comment
Между другото, array[0..4] of Byte съдържа 5 байта, а не 4. Какво е SizeOf(record) в Delphi? Предполагам, че е 20, а не 22.   -  person nullptr    schedule 01.09.2013
comment
SizeOf(TAboutData) в Delphi 2007 и XE4 дават 24, което има смисъл с подплата.   -  person Ken White    schedule 01.09.2013


Отговори (2)


Подравняването обикновено се използва от компилатора като оптимизация. Всъщност всяка структура е подплатена до кратно на 4 (или 8, не помня точно).

Актуализация: Това, което казвам по-горе относно подравняването, не е точно. Прочетете отговора на Дейвид за подробности относно това как подравнените записи се третират от компилатора. Статията в Wikipedia съдържа разумен преглед: 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);

За маршалинга използвам този метод от байт [] до структура:

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

Най-накрая намерих тази хубава статия за маршалинга.

person Community    schedule 31.08.2013
comment
Кълна се, че бях на ръба да разбера това, когато вашият Oasis of an answer свърши работата вместо мен. БЛАГОДАРЯ ТИ МНОГО! Ти си човекът! - person Danny Rodriguez; 01.09.2013
comment
Няма нужда от Pack=1. Не трябва да правите това без причина. Така че, защо? И първият ти параграф е просто неправилен. Проверете размера на структура, съдържаща един байт. - person David Heffernan; 01.09.2013
comment
Добре е да имате цялата картина, съгласен съм. Но вашият отговор не е това. Започнете с първия параграф, който е напълно неправилен. - person David Heffernan; 02.09.2013
comment
Разбирам добре и това, което казвате, е абсолютно грешно. Обясних как и в отговора си описах подравняването. Казахте Всъщност всяка структура е подплатена до кратно на 4 (или 8, не помня точно), което е абсолютно погрешно. - person David Heffernan; 02.09.2013

Ще предположа, че вашият Delphi компилатор работи в режим по подразбиране, както и подравняването на записите. Предполагам също, че вашият Pascal код съдържа проста печатна грешка, където вашият масив има 5 елемента вместо 4.

Подравняването на подравнен запис се определя от члена с най-голямо подравняване. Най-големият член е 8-байтовото двойно. Така че записът има подравняване 8. Размерът му е точно кратно на подравняването му.

Всяко отделно поле е подравнено, за да съответства на подравняването на полето. Масивът от байтове има подравняване 1 и може да се появи навсякъде. 2-байтовите цели числа трябва да се появяват на 2-байтови граници и т.н. Записите може да имат подложка в края, за да се гарантира, че масивите от записа също ще бъдат подравнени. Така записът има размер, кратен на 8. Вашият запис има размер 24.

Във вашия случай подпълването е преди 8-байтовото двойно и в края на записа. Ако тази подложка в края не беше включена, 8-байтовото двойно ще бъде неправилно подравнено във всички масиви на вашия запис.

Няма нужда да правите каквито и да е промени във вашия запис на Delphi и декларациите на C# структура. Те вече са декларирани правилно и съвпадат перфектно.

Можете да използвате SizeOf в Delphi и Marshal.SizeOf в C#, за да проверите размера на вашите структури. Ще откриете, че съвпадат с кода във въпроса.

Не мога да коментирам как вашият код се е провалил, защото не сте описали тази повреда. Моята основна гледна точка е, че всеки провал не се дължи на несъответствие между вашите структури. Няма рационално обяснение за източника на числото 22. Бих потърсил откъде идва това число.

И накрая, отговорът, който приехте, предлага използването на пакетирани структури. Няма нужда да го правите, не виждам причина да го правите и това не обяснява вашите проблеми.

person David Heffernan    schedule 01.09.2013
comment
Размерът 22 е дължината на пакета, който получавам от Delphi. TAboutData се попълва, след което се изпраща чрез TPipeServer.SendStream() [Не се сериализира, просто се чете в поток от байтове]. Изглежда, че липсват последните два подложки по някаква причина? Приложението Delphi е приложение със затворен код, до което нямам достъп. Трябваше да коментирам, че параметърът Pack не е необходим. Бях просто развълнуван, че имам алтернатива на StructureToPtr(), която наистина работи. - person Danny Rodriguez; 01.09.2013
comment
Не виждам алтернатива. PtrToStructure е това. - person David Heffernan; 01.09.2013
comment
За съжаление внедряването на не е алтернатива на - person Danny Rodriguez; 01.09.2013
comment
Истинският проблем, който имам с отговора, който приехте, е грубата фактическа грешка в първия параграф. Попитахте за подравняването и приехте и отговорихте, че е напълно грешен по този въпрос. - person David Heffernan; 01.09.2013
comment
@dna2 проблемът е, че изглежда не разбирате подравняването - person David Heffernan; 02.09.2013
comment
Дейвид, не съм тук, за да бъда обучаван, можеш просто да маркираш отговора ми (който редактирах вече 10 пъти), което е всичко, което се изисква тук на SO. за разбирането - person Dan; 02.09.2013
comment
@dna2 Не сте тук, за да бъдете обучавани? Глоба. Бих могъл да те науча без проблеми, но ако не искаш, научи се. - person David Heffernan; 02.09.2013
comment
Какво ще кажеш да научиш малко нетикет? Приемам критика, но бяха необходими 3/4 съобщения, за да разбера от вас какво сте сметнали за грешно. Лесно е да хвърлите „грешно е, грешно е“ или „проблемът е, че изглежда не разбирате подравняването“... това не ми помага да подобря отговора... и между другото, поведението ви е наистина е агресивен. Няма новини Заех отбранителна позиция. Ще се радвам да премахна отговора си, но просто не мога. Искаш ли сам да го маркирам? Ще успокои ли гнева ти към мен? - person Dan; 02.09.2013
comment
Също така добавено уики на общността. Ако разбирам за какво служи, вече трябва да можете да го редактирате свободно. - person Dan; 02.09.2013
comment
@dna2 Не ми трябва уики на общността, за да редактирам отговора ви. Имам достатъчно представители, за да го редактирам. Но не искам да пренаписвам изцяло отговора. Защото тогава ще изглежда точно като моя. И етикетът тук изисква да не пренаписвате приетите отговори. Още от самото начало обясних какво не е наред. Не в коментар, а в моя отговор. Добавих моя отговор само за да коригирам недостатъците на вашия. Не ми харесва, ако приетият отговор е много грешен. Сигурен съм, че и на вас не ви харесва. Изглеждате ми много почтен и наистина не се опитвам да бъда агресивен. - person David Heffernan; 02.09.2013