We use packaged structures that are directly superimposed directly on the binary package in memory today, and I regret that I decided to do this. The only way we got this is:
- careful definition of bit-specific types based on the compilation environment (
typedef unsigned int uint32_t ) - inserting the appropriate pragmas for a particular compiler to indicate close packing of structural elements.
- requiring everything to be in one byte order (use network or large order)
- carefully writing server and client code
If you are just starting out, I would advise you to skip the whole mess trying to imagine what's on the wire with the structures. Just arrange each primitive element separately. If you decide not to use an existing library like Boost Serialize or middleware like TibCo, then save a lot of headache by writing an abstraction around the binary buffer that hides the details of your serialization method. The purpose for the interface:
class ByteBuffer { public: ByteBuffer(uint8_t *bytes, size_t numBytes) { buffer_.assign(&bytes[0], &bytes[numBytes]); } void encode8Bits(uint8_t n); void encode16Bits(uint16_t n);
Each of your package classes will have a serialization method in ByteBuffer or deserialized from ByteBuffer and offset. This is one of those things that I absolutely wish that I could return on time and correct. I cannot count the number of times that I spent time debugging a problem caused by forgetting to exchange bytes or not packing a struct .
Another hurdle to avoid is using union to represent bytes or memcpy for an unsigned buffer to extract bytes. If you always use Big-Endian on the wire, then you can use simple code to write bytes to the buffer and not worry about htonl things:
void ByteBuffer::encode8Bits(uint8_t n) { buffer_.push_back(n); } void ByteBuffer::encode16Bits(uint16_t n) { encode8Bits(uint8_t((n & 0xff00) >> 8)); encode8Bits(uint8_t((n & 0x00ff) )); } void ByteBuffer::encode32Bits(uint32_t n) { encode16Bits(uint16_t((n & 0xffff0000) >> 16)); encode16Bits(uint16_t((n & 0x0000ffff) )); } void ByteBuffer::encode64Bits(uint64_t n) { encode32Bits(uint32_t((n & 0xffffffff00000000) >> 32)); encode32Bits(uint32_t((n & 0x00000000ffffffff) )); }
This remains a beautiful platform agnostic, as the numerical representation is always logically Big-Endian. This code is also very suitable for using templates based on the size of a primitive type (think encode<sizeof(val)>((unsigned char const*)&val) ) ... not so beautiful, but very, very easy to write and maintain.
source share