Десериализовать байтовый массив в структуру

Я получаю передачу по сети в виде массива символов / байтов. Он содержит заголовок и некоторые данные. Я хотел бы сопоставить заголовок со структурой. Вот пример:

#pragma pack(1)

struct Header
{
    unsigned short bodyLength;
    int msgID;
    unsigned short someOtherValue;
    unsigned short protocolVersion;
};

int main()
{
    boost::array<char, 128> msgBuffer;
    Header header;

    for(int x = 0; x < sizeof(Header); x++)
        msgBuffer[x] = 0x01; // assign some values

    memcpy(&header, msgBuffer.data(), sizeof(Header));

    system("PAUSE");    

    return 0;
}

Всегда ли это будет работать, если структура никогда не содержит полей переменной длины? Есть ли независимый от платформы / идиоматический способ сделать это?

Примечание.

Я видел довольно много библиотек в Интернете, которые позволяют вам сериализовать / десериализовать, но у меня сложилось впечатление, что они могут десериализовать что-то только в том случае, если оно было ранее сериализовано с той же библиотекой. Ну, я не контролирую формат передачи. Я определенно собираюсь получить массив байтов / символов, в котором все значения просто следуют друг за другом.


person drby    schedule 06.02.2009    source источник


Ответы (6)


Простое копирование с большой вероятностью сломается, по крайней мере, если данные могут поступать из другой архитектуры (или даже из компилятора), чем та, на которой вы находитесь. Это по причинам:

Эта вторая ссылка специфична для GCC, но применима ко всем компиляторам.

Я рекомендую читать поля побайтно и собирать более крупные поля (целые числа и т. Д.) Из этих байтов. Это дает вам контроль над порядком байтов и заполнением.

person unwind    schedule 06.02.2009

Некоторые процессоры требуют, чтобы определенные типы были правильно выровнены. Они не примут указанную упаковку и сгенерируют аппаратную ловушку.

И даже в обычных упакованных структурах x86 код может работать медленнее.

Также вам нужно будет соблюдать осторожность при работе с разными платформами с порядком байтов.

Кстати, если вам нужен простой и независимый от платформы механизм связи с привязкой ко многим языкам программирования, взгляните на YAMI.

person Anonymous    schedule 06.02.2009

Директива #pragma pack(1) должна работать в большинстве компиляторов, но вы можете проверить, определив, насколько большой должна быть ваша структура данных (10 в вашем случае, если мои математические расчеты верны) и используя printf("%d", sizeof(Header));, чтобы проверить, выполняется ли упаковка.

Как говорили другие, вам все равно нужно опасаться Endianness, если вы переходите между архитектурами.

person Mark Pim    schedule 06.02.2009

Я категорически не согласен с идеей чтения побайтово. Если вы позаботитесь об упаковке структуры в объявлении структуры, вы можете без проблем скопировать ее в структуру. Для проблемы с порядком байтов снова побайтовое чтение решает проблему, но не дает вам общего решения. Этот метод очень неудачный. Я делал что-то подобное раньше для аналогичной работы, и все работало нормально, без сбоев.

Думать об этом. У меня есть структура, у меня также есть соответствующее определение этой структуры. Вы можете построить это вручную, но я написал для этого парсер и использовал его также для других целей.

Например, определение структуры, которое вы дали выше, - «s i s s». (s = short, i = int) Затем я передаю адрес структуры, это определение и параметр упаковки структуры этой структуры специальной функции, которая имеет дело с порядком байтов, и готово.

SwitchEndianToBig (& header, "s i s s", 4); // 4 = вариант упаковки структуры

person Malkocoglu    schedule 06.02.2009

Скажите, если я ошибаюсь, но AFAIK, делая это таким образом, вы гарантируете, что данные верны - при условии, что типы имеют одинаковый размер на разных платформах:

#include <array>
#include <algorithm>

//#pragma pack(1) // not needed

struct Header
{
    unsigned short bodyLength;
    int msgID;
    unsigned short someOtherValue;
    unsigned short protocolVersion;
    float testFloat;

    Header() : bodyLength(42), msgID(34), someOtherValue(66), protocolVersion(69), testFloat( 3.14f ) {}
};

int main()
{
    std::tr1::array<char, 128> msgBuffer;
    Header header;

    const char* rawData = reinterpret_cast< const char* >( &header );

    std::copy( rawData, rawData + sizeof(Header), msgBuffer.data()); // assuming msgBuffer is always big enough

    system("PAUSE");    

    return 0;
}

Если типы различаются на ваших целевых формах пластин, вы должны использовать псевдонимы (typedef) для каждого типа, чтобы быть уверенным, что размер каждого используемого типа одинаков.

person Klaim    schedule 06.02.2009
comment
Хм, вы идете наоборот (преобразовываете структуру в массив байтов). И даже это не совсем работает так, как должно, потому что вы копируете заполнение структуры в массив. - person drby; 09.02.2009

Я знаю, с кем общаюсь, поэтому мне особо не нужно беспокоиться о порядке байтов. Но мне все равно нравится держаться подальше от команд, специфичных для компилятора.

Так как насчет этого:

const int kHeaderSizeInBytes = 6;

struct Header
{
    unsigned short bodyLength;
    unsigned short msgID;
    unsigned short protocolVersion; 

    unsigned short convertUnsignedShort(char inputArray[sizeof(unsigned short)])
        {return (((unsigned char) (inputArray[0])) << 8) + (unsigned char)(inputArray[1]);}

    void operator<<(char inputArray[kHeaderSizeInBytes])
    {
        bodyLength = convertUnsignedShort(inputArray);
        msgID = convertUnsignedShort(inputArray + sizeof(bodyLength));
        protocolVersion = convertUnsignedShort(inputArray + sizeof(bodyLength) + sizeof(msgID));
    }
};

int main()
{
    boost::array<char, 128> msgBuffer;
    Header header;

    for(int x = 0; x < kHeaderSizeInBytes; x++)
        msgBuffer[x] = x;

    header << msgBuffer.data();

    system("PAUSE");    

    return 0;
}

Избавляется от прагмы, но это не так универсально, как хотелось бы. Каждый раз, когда вы добавляете поле в заголовок, вам необходимо изменить функцию ‹**. Можете ли вы как-то перебрать поля структуры, получить тип поля и вызвать соответствующую функцию?

person drby    schedule 06.02.2009
comment
Об итерации по полям структуры: нужно ли использовать структуру? Я спрашиваю, потому что замена его кортежем позволит итерацию полей. - person Éric Malenfant; 06.02.2009