Могу ли я сделать одну двоичную запись для структуры С++, которая содержит вектор

Я пытаюсь создать и написать двоичный запрос и задать вопрос типа «возможно ли это». Для меня может быть важно упомянуть, что получатель запроса не знает о структуре данных, которую я включил ниже, он просто ожидает последовательность байтов, но использование структуры показалось удобным способом подготовить части запроса, тогда легко напишите их.

Написание верхнего и нижнего колонтитула в порядке, поскольку они имеют фиксированный размер, но у меня возникают проблемы со структурой «Подробности» из-за вектора. На данный момент я пишу в файл, поэтому я могу проверить, что запрос соответствует спецификации, но намерение состоит в том, чтобы в конечном итоге записать в ПЛК, используя последовательный порт boost asio.

Я могу использовать такой синтаксис, чтобы написать структуру, но она записывает адреса указателей, а не значения, когда попадает в вектор.

myFile.write((char*) &myDataRequest, drSize);

Я могу использовать этот синтаксис для записи вектора отдельно, но я должен включить индексатор в 0, чтобы записать значения

myFile.write((char*) &myVector[0], vectorSize);

Есть ли элегантный способ двоичной записи структуры, содержащей вектор (или другую подходящую коллекцию), делая это за один раз? Скажем, например, если я объявил вектор по-другому, или я смирился с многократными записями содержимого внутри структуры. Если я заменю вектор массивом, я могу отправить структуру за один раз (без необходимости включать какой-либо индексатор), но я не знаю требуемый размер до времени выполнения, поэтому я не думаю, что это подходит.

Моя структура

    struct Header
    {   ...     };

    struct Details
    {
           std::vector<DataRequest> DRList;
    };

    struct DataRequest
    {
           short numAddresses;          // Number of operands to be read   Bytes 0-1
           unsigned char operandType;   //                                  Byte 2
           unsigned char Reserved1;     //Should be 0xFF                    Byte 3
           std::vector<short> addressList;  // either, starting address (for sequence), or a list of addresses (for non-sequential) 
    };

    struct Footer
    {   ...     };

person S P    schedule 25.09.2012    source источник


Ответы (3)


Это невозможно, потому что объект std::vector на самом деле не содержит массив, а скорее указатель на блок памяти. Однако у меня возникает соблазн заявить, что возможность писать такую ​​необработанную структуру нежелательна:

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

  2. В зависимости от того, что вы пишете, вы можете обнаружить, что записи в любом случае буферизуются, поэтому множественные вызовы записи на самом деле не менее эффективны.

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

Короче говоря: я подозреваю, что выписывать каждое поле по одному не менее эффективно, но и более правильно.

person Frerich Raabe    schedule 25.09.2012
comment
спасибо, это все, что я ищу здесь, несколько советов по правильной стратегии. - person S P; 25.09.2012

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

Если вам нужно удобство для сортировки/десортировки, вам следует взглянуть на библиотека boost::serialization. Они предлагают двоичный архив (помимо текста и xml), но он имеет свой собственный формат (например, у него есть номер версии, какая библиотека сериализации использовалась для дампа данных), поэтому, вероятно, это не то, что хочет ваш клиент.

person count0    schedule 25.09.2012

Какой именно формат ожидается на другом конце? Вы должны написать это, и точка. Вы не можете просто записать любые случайные байты. Вероятность того, что простое написание std::vector, как вы делаете, сработает, настолько близка к 0, насколько это возможно. Но вероятность того, что запись struct только с int сработает, по-прежнему меньше 50%. Если другая сторона ожидает определенную последовательность байтов, вы должны записать эту последовательность байт за байтом. Например, чтобы написать int, вы все равно должны написать четыре (или сколько угодно, что требует протокол) байт, что-то вроде:

byte[0] = (value >> 24) & 0xFF;
byte[1] = (value >> 16) & 0xFF;
byte[2] = (value >>  8) & 0xFF;
byte[3] = (value      ) & 0xFF;

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

Обычно, конечно, вы строите свой буфер в std::vector<char>, а затем пишете &buffer[0], buffer.size(). (Тот факт, что вам нужен reinterpret_cast для указателя буфера, должен сигнализировать о том, что ваш подход неверен.)

person James Kanze    schedule 25.09.2012
comment
Ваш код на самом деле эквивалентен htonl, я думаю. - person Frerich Raabe; 25.09.2012
comment
@FrerichRaabe Может быть. На некоторых платформах. Но, конечно, мы не знаем, нужны ли ему именно эти байты. Я просто вставил это в качестве примера того, как вы должны действовать. (И, конечно же, с htonl вам все равно придется назначать результаты uint32_t, а затем копировать их побайтно в буфер, поскольку htonl не заботится о проблемах выравнивания.) - person James Kanze; 25.09.2012