Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I am working on an embedded device (microcontroller), and I want to save objects to permanent storage (an EEPROM). Most of the serialization solutions I can find, use the file-system in some way, but my target has no file-system.

Therefore my question is, how can I serialize an object to a byte-array so I can save that byte-array to an EEPROM afterwards?

Here is an example of what i am 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; 
};
share|improve this question
    
the easiest way to serialize data is to add seperators, but that depends on the data you want to serialize. Normally you should be able to read/write your buffer byte-wise and store it the same way into your EEPROM –  Die Usche Oct 13 at 9:14

2 Answers 2

up vote 1 down vote accepted

It's likely that your code for save and load will be reasonably generic and would work best in a separate 'manager' class, leaving each data class only with the responsibility of rendering itself as re-loadable:

// 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() ];
    }
};

Each class you intend to serialize / de-serialize should inherit from an interface which mandates the virtual interface for these functions. Note that you'll need to do your own memory management here. I've given a simple example but you'd probably want something a bit more robust.

Then each function should sequentially serialize all attributes of the class (chaining bases classes and calling 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'll benefit from generic code to serialize / de-serialize each separate type so you don't keep having code to write the length of strings etc. I.e. a 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;
}

Incidentally, you might also need to consider what will happen if you change the schema of an object in a software upgrade. Your saved objects would potentially become corrupted on reloading, unless you code round this using - for example - a class version identifier.

Final thought: At a micro level, what you're doing is in many ways similar to the way POD data is serialised for network transmission, so it may be that you can take advantage of libraries to do that - even if you don't have access to an operating system.

share|improve this answer
    
Actually this is precisely the way that I already have started implementing it. I was just uncertain if there was a more elegant alternative. But this confirmation is just as good for the answer. thanks. –  Jolle Oct 13 at 12:36
    
NP. I've added some code as a general example but, I'd stress, not tested - so please treat with caution. Hopefully it is helpful to you. –  Component 10 Oct 13 at 14:23

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

Length to name
char array of name
age
weight

So the code to serial 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 binary buffer, you read its length first, and then copy its data.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.