Serializing an object to a byte array in C ++

I am working on an embedded device (microcontroller) and I want to save objects to persistent storage (EEPROM). Most serialization solutions I can find use the filesystem in some way, but my target doesn't have a filesystem.

So my question is, how can I serialize an object to a byte array so that I can store that byte array in EEPROM afterwards?

Here's an example of what I'm trying to do:

class Person{
     //Constructor, getters and setters are omitted

    void save(){
         char buffer[sizeof(Person)]; 
         serialize(buffer);
         EEPROM::Save(buffer, sizeof(Person)); 
    }

    void load(){
         char buffer[sizeof(Person)]; 
         EEPROM::Load(buffer, sizeof(Person));
         deserialize(buffer);
    }

    void serialize(char* result){
        //?????
    }

    Person deserialize(char* buffer){
        //??????
    }

private:
    char* name;  
    int   age; 
    float weight; 
};

      

+3


source to share


2 answers


Your code for save

and load

will probably be generic enough to work best in a separate "manager" class, leaving each data class only responsible for rendering itself as reloadable:

// Interface class
class Serializable
{
public:
    virtual size_t serialize_size() const = 0;
    virtual void serialize(char* dataOut) const = 0;
    virtual void deserialize(const char* dataIn) = 0;
};

// Load / save manager
class EEPromManager
{
public:
    void save( const Serializable& s )
    {
        char * data;
        size_t data_len;
        reserve_memory( data, data_len, s );
        s.serialize( data );
        EEPROM::Save( data , data_len );
        delete [] data;
    }

    void load( Serializable& s )
    {
        char * data;
        size_t data_len;
        reserve_memory( data, data_len, s );
        EEPROM::Load( data, data_len );
        s.deserialize( data );
        delete [] data;
    }

private:
    char* reserve_memory( char*& data, size_t& data_len, const Serializable& s )
    {
        return new char[ s.serialize_size() ];
    }
};

      

Every class that you plan to serialize / de-serialize must inherit from an interface that provides a virtual interface for these functions. Note that you will need to do your own memory management here. I've provided a simple example, but you probably want something more robust.

Then each function must serialize all the attributes of the class sequentially (base class chaining and call serialize

on aggregate objects, if needed.)

class Person : public Serializable
{
public:
    virtual size_t serialize_size() const
    {
        return SerializablePOD<char*>::serialize_size(name) +
               SerializablePOD<int>::serialize_size(age) +
               SerializablePOD<float>::serialize_size(weight);
    }

    virtual void serialize(char* dataOut) const
    {
        dataOut = SerializablePOD<char*>::serialize(dataOut, name);
        dataOut = SerializablePOD<int>::serialize(dataOut, age);
        dataOut = SerializablePOD<float>::serialize(dataOut, weight);
    }
    virtual void deserialize(const char* dataIn)
    {
        dataIn = SerializablePOD<char*>::deserialize(dataIn, name);
        dataIn = SerializablePOD<int>::deserialize(dataIn, age);
        dataIn = SerializablePOD<float>::deserialize(dataIn, weight);
    }

private:
    char* name;
    int   age;
    float weight;
};

      



You will end up with generic code to serialize / de-serialize each individual type so that you don't have code to write line lengths etc. Ie serialize / de-serialize for each POD type:

template <typename POD>
class SerializablePOD
{
public:
    static size_t serialize_size(POD str)
    {
        return sizeof(POD);
    }
    static char* serialize( char* target, POD value )
    {
        return memcpy( target, &value, serialize_size(value) );
    }
    static const char* deserialize( const char* source, POD& target )
    {
        memcpy( &target, source, serialize_size(target) );
        return source + serialize_size(target);
    }
};

template<>
size_t SerializablePOD<char*>::serialize_size(char* str)
{
    return sizeof(size_t) + strlen(str);
}

template<>
const char* SerializablePOD<char*>::deserialize( const char* source, char*& target )
{
    size_t length;
    memcpy( &length, source, sizeof(size_t) );
    memcpy( &target, source + sizeof(size_t), length );
    return source + sizeof(size_t) + length;
}

      

By the way, you might also need to consider what happens if you change the object schema during a software update. Your saved objects can potentially get corrupted on reload if you don't call this using a class version id, for example.

Final thought: On a micro level, what you are doing is a lot like how POD data is serialized for network transmission, so it might be that you can use libraries to do this - even if you don't have access to the operating system.

+3


source


To store a string in binary, we usually store its length and then its contents. To store other primitive data, we can simply store it in binary form. So in your case, all you need to keep is:

Length to name
char array of name
age
weight

      

So the code for the serial number is:



size_t buffer_size = sizeof(int) + strlen(name) + sizeof(age) + sizeof(weight);
char *buffer = new char[buffer_size];
*(int*)p = strlen(name);  p += sizeof(int);
memcpy(p, name, strlen(name));  p += strlen(name);
*(int*)p = age;  p += sizeof(int);
*(float*)p = weight;
EEPROM::Save(buffer, buffer_size);
delete[] buffer;

      

And to read a string from a binary buffer, first read its length and then copy your data.

+1


source







All Articles