Webocreation

Tuesday, November 24, 2009

Serializing Data Structures

Serializing Data Structures

Arbitrary data structures can be serialized by using the member functions CArchive::Write and CArchive::Read. Each of these functions takes as parameters a pointer to a buffer and the number of bytes to transfer. The following code example uses these functions to serialize a LOGFONT data structure:

void CMyFont::Serialize(CArchive& ar)
{
LOGFONT logfont;
if (ar.IsStoring())
{
// call user function to initialize the structure.
InitLogFont(&logfont);
ar.Write(&logfont, sizeof(logfont));
}
else
{
ar.Read(&logfont, sizeof(logfont));
// call user function to save the structure.
SaveLogFont(&logfont);
}
}

Serializing Class Objects
Class objects can be serialized if they have been derived from CObject and have overridden the Serialize member function. For example, many of the MFC collection classes override this function for you. To serialize a document that contains such an object, call the Serialize function of the member directly.


Making a Serializable Class
You can implement object persistence for any class by adding serialization support for that class. You add this support by first declaring the class as serializable and then implementing the necessary serialization code.


To make a serializable class

1. Publicly derive the class from CObject or from a class that was derived from CObject.
2. In the class declaration, provide a default constructor. The framework requires a default constructor (a constructor with no arguments) to create objects when loading data from a data file. If you use ClassWizard to add a new class, it will automatically add a default constructor.
3. In the class declaration, add the DECLARE_SERIAL macro. Provide the class name as the single argument.
4. In the implementation file, add the IMPLEMENT_SERIAL macro. The three parameters to IMPLEMENT_SERIAL are the class being serialized, its base class, and a schema number.
5. Override the CObject::Serialize function. This is easily done by right-clicking the class name in ClassView and then selecting the menu item Add Virtual Function.


To see sample code that declares a serializable class, click this icon.

// Serializ.h - header file

// Publicly derive the class from CObject
class CSerializedPhrase : public CObject
{
protected:
// Provide a default constructor
CSerializedPhrase();
public:
// Override Serialize
void Serialize(CArchive &);
// Invoke the DECLARE_SERIAL macro
DECLARE_SERIAL(CSerializedPhrase)
...
};

To see sample code that implements serialization for a class, click this icon.

// Serializ.cpp - implementation file
IMPLEMENT_SERIAL(CSerializedPhrase, CObject, 1)
CSerializedPhrase::CSerializedPhrase()
{

}
void CSerializedPhrase::Serialize(CArchive & ar)
{
// The base function is called first.
CObject::Serialize(ar);
if (ar.IsLoading())
ar >> m_phrase >> m_location >> m_color;
else
ar << m_phrase << m_location << m_color;
}

Using a Serializable Class
How you use a serializable class depends upon whether you are working with an object of the serializable class or a pointer to an object of the serializable class. This topic also covers how to handle issues of schema numbering both in the serializable class and in the document class.

Serializing Objects
If your document class uses your serializable class as an embedded member, you can invoke the Serialize method directly, as shown in the following example code:

class CPhraseDoc : public CDocument
{
...
// Define a CSerializedPhrase object
CSerializedPhrase m_Phrase;
...
}
// CPhraseDoc.cpp implementation file
void CPhraseDoc::Serialize(CArchive& ar)
{
m_Phrase.Serialize(ar);
}

Serializing Objects via Pointers

Using a pointer for the serializable object has its own set of considerations. How you handle the serialization depends upon whether or not the pointer was initialized.

Deserializing to Uninitialized Object Pointers

If the class contains an uninitialized object pointer, then you should rely on the extraction and insertion operators (>> and <<) for serialization of the object. In the case of reading from the data file, the archive object will build the object using its default constructor and then call the object's Serialize function. As an alternative, you can explicitly construct the object pointer and then call the Serialize function. To see sample code that uses the archive to handle memory allocation when you deserialize an object, click this icon.

// PhrasDoc.h
class CPhraseDoc : public CDocument
{
...
// Define a CSerializedPhrase pointer
CSerializedPhrase * m_pPhrase;
...
}
// PhrasDoc.cpp
CPhraseDoc :: CPhraseDoc()
:m_pPhrase(0)
{

}
void CPhraseDoc::Serialize(CArchive& ar)
{
if (ar.IsLoading())
ar >> m_pPhrase;
else
ar << m_pPhrase;
}
BOOL CPhraseDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_pPhrase = new CSerializedPhrase;
// fill object as appropriate
return TRUE;
}
void CPhraseDoc::DeleteContents()
{
delete m_pPhrase;
m_pPhrase = 0;
CDocument::DeleteContents();
}

Deserializing to Initialized Object Pointers

If the object pointer is already initialized, then you can use either the extraction and insertion operators (>> and <<), or you can call the object's Serialize function directly. What is important is that you make sure that the object cleans up its data members before deserializing from the data file. Not adhering to this rule will generate memory leaks in your application. Cleaning up an object's data members is often done by calling a user-defined member function that frees memory allocated from the heap.

The document object provides a convenient place for deleting its contents in the CDocument::DeleteContents member function. The MFC framework is structured so that it will call this function when a document is to be reused or destroyed. A useful strategy is to create a DeleteContents member function for each serialized class that you create and then have each object call the DeleteContents function of each of its contained objects. Since the framework calls the document's DeleteContents member function, the entire document will be efficiently and correctly deleted.


Whichever method you decide to use, you must be consistent. Use the same method — either the Serialize function or the CArchive << and >> operators — for both the loading and storing of any one object.

No comments:

Post a Comment