MFC serialization is a sophisticated method to manage saving and loading your MFC objects and primitive data with files.
Basics of Serialization
There are three main classes involves in the serialization process, CArchive object, a CFile object and the object you want to write.
The first step in the serialization process is to create a serialize-able class.
class EmployeeInfo : public CObject
{
public:
// Initialize serialization for the class
DECLARE_SERIAL(EmployeeInfo)
CString m_csName;
CString m_csDesignation;
int m_nEmpID;
int m_nAge;
int m_nSalary;
…
// Override the base class functions to implement serialiazation
virtual void Serialize( CArchive& ar );
public:
// Provide default constructor
EmployeeInfo();
~EmployeeInfo();
// Define operator =
EmployeeInfo& operator =(const EmployeeInfo & empInfo_i );
};
// Implement serialization support
void EmployeeInfo::Serialize( CArchive& ar )
{
if( ar.IsStoring())
{
ar << m_csName;
ar << m_csDesignation;
ar << m_nEmpID;
…
}
else
{
ar >> m_csName;
ar >> m_csDesignation;
…
}
}
Do the serialization for the Object
CFile file( “EmployeeInfo.bin”, CFile::modeCreate | CFile::modeWrite );
CArchive ar( &file, CArchive::store );
EmployeeInfo empInfo;
empInfo.Serialize( ar );
ar.Close();
file.Close();
Later de-serialize (load back from file)
CFile file( “EmployeeInfo.bin”, CFile::modeRead | CFile::typeBinary );
CArchive ar( &file, CArchive::load );
m_EmployeeInfoArr.Serialize( ar );
ar.Close();
file.Close();
Under the hood of CArray of EmployeeInfo (or any ) class
If I create an array of my serializable object and then I archive and back from file, what happens under the hood?
The following snippet is used to implement serialize an array objects.
// Declare an array of objects
CArray<EmployeeInfo> m_EmployeeInfoArr;
// Serialize them
CFile file( “EmployeeInfo.bin”, CFile::modeCreate | CFile::modeWrite );
CArchive ar( &file, CArchive::store );
m_EmployeeInfoArr.Serialize( ar ); // Serialize
ar.Close();
file.Close();
While we serialize the object the data is being stored in the file as bitwise copy. The pointers and the other MFC objects are the catch in this kind of serialization. For e.g CString objects allocates memory in heap and stores the handles and book keeping information only inside the object. So when we do a bitwise memory copy on these kind of objects, the just the pointer or handle values are being stored inside the file. Later when we restore it, the handles or pointers become invalid to restore the real content of the array. For this purpose, we have to make CArray ::Serialize function to call the Serialize function we implemented for the serializable object, then we can ensure proper serialization and deserialization.
So What it does inside the Serialize Function of CArray?
template<class TYPE, class ARG_TYPE>
void CArray<TYPE, ARG_TYPE>::Serialize(CArchive& ar)
{
…
CObject::Serialize(ar);
if (ar.IsStoring()) // Write count first
{
ar.WriteCount(m_nSize);
}
…
// Write the content of the object
SerializeElements<TYPE>(ar, m_pData, m_nSize);
}
Inside the function it calls SerializeElements which does a memory copy of CArray Object. Here’s the catch, it’s calling a template function based on the type we have used to create the CArray Object. So if we could give a specialized SerializeElements function for our object, before generating this object, then we can manage the serialization of CArray Object for its entire elements.
Before declaring (or compiler generating the code for) the CArray<> object, declare/define the specialized global function. Note that this is not a member function, but a specialized global function for our serializable Object.
template<> void AFXAPI SerializeElements( CArchive& ar, EmployeeInfo* pElements, int nCount)
{
for (int i=0;i < nCount; i++,pElements++) // Serialize each objects
{
pElements->Serialize(ar);
}
}
The above template specialization will iterate through the entire elements of array and call serialize function of each objects inside. Inside the Serialize function of EmployeeInfo class, we are dealing with storing and reading.