Как считать данные из объекта IO в структуру данных?

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

Самой большой проблемой является то, что данные из IO-объектов могут удобным образом считываться только в типы данных, представленные классам, корректно реализующими некий from_io метод. Такого рода заморочки для решения элементарной и очевидной задачи мне показались некоторым перегибом, а поскольку сопутствующего головокружения от успехов у меня не наступало, я решил поискать другой способ.

Вот, например: можно считать данные из объекта IO (в моём случае — сокета) в некий инстанс типа Bytes. Изучаем API языка — и узнаём о том, что Bytes — это ни что иное как Slice(UInt8). Ну легче конечно не стало, но продолжаем бороздить космические глубины — и натыкаемся на то, что Slice — это вообще-то разновидность указателя Pointer, только с жёстко привязанной к нему логикой проверки границ. Узнав о таких перипетиях бытия, я решил было, что смогу сделать Slice, указывающий на экземпляр, он же инстанс, моей гипотетической структуры данных, а потом просто записать в него данные их сокета. Но конечно же не тут-то было: считать-то можно, но только в Slice(UInt8), если это Slice(C::SomeStruct), например, то Crystal посетует на отсутствие подходящего метода для IO — и откажется компилировать программу. Но лиха беда начало, добрался я и до «родительского» по отношению к Slice классу Pointer. А тот, как назло, тоже строго привязан к типу (при создании нужно указывать тип данных) и порождает, соответственно, Slice(T), где T — это тот же тип, что и в порождающем Pointer'е. Думал я думал — и придумал, что раз я не могу просто создать Pointer на экземпляр структуры данных, куда следует записать данные из IO, то я ведь могу создать 2 указателя: один на экземпляр структуры, а другой, вспомогательный, на UInt8. И это оказалось верным решением, потому что Pointer, к счастью огромному, при создании не требует сразу указывать нечто определённое с присущим этому определённому типом T. Оказывается, указатель даже на Crystal можно создать просто под адресу, т.е. указав некое смещение в памяти. Ну а что же в таком случае мешает получить какое угодно количество указателей, формально относящихся к произвольным типам данных и указывающих, например, на одно и то же место в памяти? Да ничто не мешает!

Осознав всё сказанное выше, я реализовал запись из сокета в структуру данных буквально вот так, просто и элегатно:


lib C
   struct ZbxSenderHdr
     z_sign : StaticArray(UInt8, 4)
     z_stop_byte : UInt8
     z_payload_l : UInt8
   end
end
zhdr=C::ZbxSenderHdr.new
sock.read(Slice.new(Pointer(UInt8).new(pointerof(zhdr).address), sizeof(C::ZbxSenderHdr)))


Т.е. я создал Slice из Pointer(Uint8), который в действительности указывает туда же, куда и pointerof(zhdr) — и записал по адресу Slice'а данные, считанные из сокета.

Не знаю уж, насколько всё это может быть наглядным, но надеюсь, что кому-то кроме меня может оказаться полезным :)

Засим позвольте откланяться — и до новых встреч!

0 комментариев