22 Serializaton2004 PDF
22 Serializaton2004 PDF
Deserialization means converting such a stream of bytes back into a data object. One common use of serialization is in saving a document on disk. The documents data must be converted to a stream in order to be written to a file. A corresponding deserialization takes place when the file is opened and a document created from the data stored in the file.
Another use of serialization is in distributed computing. If we want to access an object on a remote computer, the object must be serialized for transmission across a network, and deserialized at the destination computer. Thus serialization is at the heart of remote procedure invocation and related concepts in distributed objectoriented programming.
MFC offers support for serialization and makes it comparatively easy to implement File | Save and File | Open in your programs. The following points explain how it works.
CArchive A key class is CArchive. MFC creates an object of this class when the user chooses File | Save or File | Open. This archive object buffers data for the users selected file. Note that MFC will open the file invisibly; you dont have to worry about the file at all. You do not even have to manipulate a CFile object. The closest you will come to the file is the CArchive object. The CArchive class has a member IsStoring which tells whether the archive has been opened for reading or writing. In case the IsStoring member is true, you write to the archive, and in case it is false, you read from the archive. The place where you do this reading and writing is in Serialize. This function belongs to the CObject class, the base of the entire MFC class hierarchy. You should override it in every class that forms part of your documents data. That is, every object that forms part of your document must be serializable.
When the user chooses File | Open, the resulting command message is mapped to CWinApp::OnFileOpen, which lets the user select a file using a Windows common File Open dialog. opens the file calls the document class member function OnOpenDocument, which calls DeleteContents (to clean out any previous document data) creates a CArchive object, and calls the documents Serialize. The IsStoring member of the archive is set to FALSE.
When the user chooses File | Save As (or File | Save for the first time), the CDocument member function OnFileSave is called. This brings up a File Save As dialog, which allows the user to select a file name. creates an archive object and calls the documents Serialize, with the IsStoring member set to TRUE.
Making a CFormView based application To make the program as simple as possible, we use a CFormView based SDI application. Note: I first thought that such applications had been replaced by dialog based applications in Visual Studio.NET. But such an application CAN still be made with Visual Studio.NET. It is similar to a dialog-based application, but has a menu automatically. To make one, choose SDI and then go to Generated Classes in the application wizard, and specify CFormView as the base class for your view class:
When you click Finish it tells you that you wont get printing support. OK, we dont need to print from this example anyway.
An example to demonstrate serialization The simplest example of a program with some data would have just one data item, say CString m_data, a member variable of the document class. Make a CFormView based application, and just put a single edit box on the form, in which the single data string will be displayed. Make the edit box big enough to hold many lines of text, and set its Multiline and WantReturn properties to true:
Add a variable m_edit of category value and type CString corresponding to your edit box. Build the program. You can enter and edit several lines of text. Because you set the WantReturn property to true, the Enter key gives you a new line in the edit box, instead of dismissing the dialog. Heres a picture of the program running at this stage of development:
This program, like all programs based on CFormView, is not directly compatible with the document-view architecture, since the data is so kept in member variables in the view class associated with dialog controls. But serialization works with the document. MFC will automatically call the Serialize function in the document class, so it works easily to save and restore the document data. Therefore, we should have some document data to save. As in your Planets program, well put in a SaveData button that will transfer the data to the document class. Heres what it looks like after that:
Add a CString variable m_data to the document class, and a handler for the button as follows:
void CSerializationDemoView::OnBnClickedButton1() { CSerializationDemoDoc *pDoc = GetDocument(); UpdateData(TRUE); pDoc->m_data = m_edit; }
Now edit Serialize in your document class. Make it look like this:
void CSerializationDemoDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << m_data; }
The call to UpdateAllViews is needed to help us get the data back into the edit box. This will call the view classs OnUpdate method. Override that method and put in this code:
void CSerializationDemoView::OnUpdate(CView* /*pSender*/, LPARAM /*lHint*/, CObject* /*pHint*/) { CSerializationDemoDoc *pDoc = GetDocument(); m_edit = pDoc->m_data; UpdateData(FALSE); }
This moves the data from the document to m_edit and finally into the edit box itself. Now you can run the program and test it. Enter giraffe and save the file, then close the program. Start it again and open the saved file. Do you see giraffe again? You should. Now change the text to "elephant". Again open the saved file, without saving first. What do you think you'll get? If this were Notepad, you would get asked whether you want to save your changes or not:
In an MFC SDI application, this doesn't happen (at least by default). It just assumes you want to keep your changes, so "elephant" remains on the screen. But "giraffe" is still in the file, as you can verify by exiting the program without saving and starting again. In other words, opening the file that is already open has no effect, instead of reverting to the saved version. You could change that behavior by programming, but this is the default SDI behavior. Serializing a Class Every class in the MFC hierarchy has its own Serialize. For example CRect has a Serialize member. The << and >> operators are overloaded to call Serialize when writing to or reading from archives. Thus objects of type CString, CRect, etc., as well as numbers, can simply be written to and read from archives by << and >>. But any reasonably complex program will also involve some programmer-defined classes that need to be serialized. This involves two steps: writing a Serialize member function for the class. (This overrides the member function in the CObject class.) overload >> and << so they call Serialize.
This is not done directly, but by means of macros supplied by MFC. Use the macro DECLARE_SERIAL(CMyClass) in the header file (in the declaration of the class, after protected:) Use the IMPLEMENT_SERIAL(CMyClass,CObject,0) macro in the .cpp file (at the top, outside any function). These macros expand to code that overloads the << and >> operators for reading to and writing from CArchive objects, using the Serialize function. Writing Serialize The basic idea is that to serialize a class, we serialize all its members. If some of those are classes, in turn their member variables will be serialized. Eventually we get down to classes whose members are basic data items (numbers, characters, strings). Let's say you have a class Person which contains a member variable m_Credit of type CreditHistory. For simplicity assume the only other member of Person is CString m_Name. void Person::Serialize(CArchive& ar) { if(ar.IsStoring()) ar << m_Name ; else ar >> m_Name; m_Credit.Serialize(ar); } You don't write ar << m_Name << m_Credit.
Serializing Pointers Pointers are a problem. If you save a pointer (an address), and later restore it from the file, it will not be a valid pointer. You must save the data pointed to, not the pointer. Then, when reading from the archive, you must allocate a new object and fill in its fields with the data. But if all you saved was the data, how will you know what kind of object to allocate when reading the file? Let's suppose Person has members m_Name, as above, and mp_Credit, which is a pointer to CreditHistory, instead of an embedded object. Then you could write: void Person::Serialize(CArchive& ar) { if(ar.IsStoring()) ar << m_Name; else { ar >> m_Name; mp_Credit = new CreditHistory; } mp_Credit->Serialize(ar); }
But you can also write the simpler code void Person::Serialize(CArchive& ar) { if(ar.IsStoring()) ar << m_Name << mp_Credit; else ar >> m_Name >> mp_Credit; } This works because >> and << are overloaded for pointers to CreditHistory, thanks to the macros DECLARE_SERIAL and IMPLEMENT_SERIAL in the CreditHistory class. These macros ensure that the name of the class is saved in the file along with the data, and that when the data for mp_Credit is read out of the file, a new CreditHistory object is created to hold it, and the address of that object placed in mp_Credit, just as in the previous code example.
The "dirty flag" The document class has a member function m_bModified, known as the dirty flag. You should set it to TRUE using SetModifiedFlag(TRUE) when the document data is changed. You can check it using IsModified(). For example, you might disable the Save button on the toolbar when the document is clean, using a CmdUI handler in which you call IsModified.
Planning for version changes Desired: the old versions of your program can open documents made with newer versions, and vice-versa. Solution: Store the version number as part of the document data. Store it first and retrieve it first. If new fields (members) are added in newer versions, then the deserialization code, after reading the version number, can initialize the new fields with default values. Store the new fields AFTER all old fields. As long as the old fields are still used in the same way, the older versions of the program should be able to deserialize the document correctly.
The collection classes CArray and CObList have a Serialize member, so you can easily serialize a CArray of CString, for example. If you write Serialize for a class of your own, you automatically can serialize an array or list of objects of that class. This is one important reason for learning how to use the collection classes. A CArray is a dynamic array. That is, it can be used like an array, but there is no fixed limit on the number of elements it can contain. It will be allocated originally at a certain size, and if more space is needed, it will be reallocated with more space, and the original contents copied if necessary. (Therefore it cannot contain pointers to other objects stored in the array--such pointers would become invalid when copied. Use dynamic arrays only for objects that do not contain pointers.)
The only tricky part of using a CArray is the declaration. You can have a CArray of ints, or a CArray of CString objects, etc. That means CArray is a template class. To get an array of integers you would use CArray<int, int&> myArray; Before using the array you need to set its initial size: myArray.SetSize(100); Now the effect is similar to int myArray[100]; except that you can harmlessly put something in myArray[101] , and you can effortlessly serialize myArray. Some member functions of the CArray class: GetSize() will return the number of elements in the array. (Not the number of presently-allocated spaces to hold elements, which generally will be larger.) RemoveAll() empties the array. After that, GetSize returns 0. There are other member functions, such as InsertAt, SetAt, ElementAt, etc., but the operator [] is overloaded so that you can use a CArray just like an ordinary array. Hence you seldom need the member functions.
Serializing CArrays
You call your array's Serialize function directly, the << and >> operators are not used. (The same as for any embedded object.) When a collection class list is a member of your document, and it is deserialized, the new data are appended to any existing data. You can call DeleteContents to prevent this when you dont want it.