Electively serialize to/from XML or binary in C++

Binary Serialization

Now let’s have look at serialization. Assume that we’re handing over a preallocated buffer and a buffer size where the data is to be serialized to, how would you know how big the buffer should be? You could use a std::vector internally instead which keeps growing with the serialized data you create, but this would lead to an unnecessary continuous buffer reallocation whenever the vector has to resize.
A much simpler idea is to create a Serializer which does nothing but count the amount of memory required.

#ifndef VTE_CORE_BINARYSERIALIZATIONSIZE_H
#define VTE_CORE_BINARYSERIALIZATIONSIZE_H
#include "Serializer.h"
namespace vte
{
namespace core
{
class BinarySerializationSize : public Serializer
{
public:
	BinarySerializationSize()
	:	length( 0 )
	{
	}
	void beginAttribute( const std::string& name )
	{
	}
	void writeNumberOfElements( u32 numElements )
	{
		length += 4;
	}
	void writeParameter( const std::string& name, const std::string& value )
	{
		// Four bytes for the length, plus string data
		length += 4 + (u32) value.length();
	}
	void writeParameter( const std::string& name, s32 value, const std::string& readable )
	{
		length += 4;
	}
	void writeParameter( const std::string& name, const bool& value )
	{
		// Bools are saved as one byte
		length++;
	}
	...
	void endAttribute()
	{
	}
	u32 getSize() const
	{
		return length;
	}
private:
	u32 length;
};
} // namespace core
} // namespace vte
#endif

Using an instance of BinarySerializationSize you would simple trigger the serialization process once, read the required size from getSize, allocate a chunk of memory and then do the actual serialization using the BinarySerializer.

#ifndef VTE_CORE_BINARYSERIALIZER_H
#define VTE_CORE_BINARYSERIALIZER_H
#include "Serializer.h"
namespace vte
{
namespace core
{
class BinarySerializer : public Serializer
{
public:
	BinarySerializer( u8* buffer, u32 length );
	virtual ~BinarySerializer();
	void beginAttribute( const std::string& name )
	{
	}
	void writeNumberOfElements( u32 numElements );
	void writeParameter( const std::string& name, const std::string& value );
	void writeParameter( const std::string& name, s32 value, const std::string& readable );
	void writeParameter( const std::string& name, const bool& value );
	void writeParameter( const std::string& name, const u32& value );
	void writeParameter( const std::string& name, const f32& value );
	void writeParameter( const std::string& name, const vec3& value );
	void writeParameter( const std::string& name, const irr::video::SColorf& value );
	void endAttribute()
	{
	}
	bool isOverflowed() const
	{
		return overflow;
	}
protected:
	u8* buffer;
	u32 bufferSize;
	u32 pointer;
	bool overflow;
};
} // namespace core
} // namespace vte
#endif

This looks pretty much like the BinaryDeserializer, so I won’t get into the details here. Like the declaration, the definition is also quite similar to the BinaryDeserializer.

vte::core::BinarySerializer::BinarySerializer( u8* buffer, u32 bufferSize )
:	buffer( buffer ),
	bufferSize( bufferSize ),
	pointer( 0 ),
	overflow( false )
{
}
vte::core::BinarySerializer::~BinarySerializer()
{
}

We have our buffer passed in again, remember the buffer and its size plus our current position in the data and an overflow flag.

void vte::core::BinarySerializer::writeNumberOfElements( u32 numElements )
{
	VTE_ASSERT( !overflow );
	if( overflow || pointer + 4 > bufferSize )
	{
		overflow = true;
		return;
	}
	memcpy( (void*)( buffer + pointer ), &numElements, 4 );
	pointer += 4;
}
void vte::core::BinarySerializer::writeParameter( const std::string& name, const std::string& value )
{
	VTE_ASSERT( !overflow );
	u32 len = (u32) value.length();
	writeNumberOfElements( len );
	if( overflow || pointer + len > bufferSize )
	{
		overflow = true;
		return;
	}
	memcpy( (void*)( buffer + pointer ), value.c_str(), len );
	pointer += len;
}

Writing values into the binary buffer works like reading, but with memcpy‘ing from the source value into the buffer instead of the other way around. Whenever an overflow is detected, the methods leave to avoid memory corruption.

4 Comments

Leave a Reply

Your email address will not be published.