Lesson 2: Serialization of Application Data

Implementing persistence presents special problems in development of object-oriented applications. A development team must consider how to preserve the structure and relationships of application objects when storing and retrieving persistent data. This consideration can be troublesome, since data in a file is typically stored as an unstructured binary stream. To address this problem, the MFC application framework implements serialization, which enables you to preserve your application data's object structure when saving it to, and restoring it from, a persistent archive.

After this lesson, you will be able to:

  • Understand how the MFC application framework implements serialization.
  • Use the overloaded << and >> operators to serialize built-in types and MFC types to an archive.
  • Make a class serializable.
  • Serialize an MFC collection.
Estimated lesson time: 50 minutes

MFC Support for Serialization

MFC provides built-in support for serialization through the CObject class. All classes that implement serialization must derive from CObject and must also provide an overload of the CObject::Serialize() function. The Serialize() function's task is to archive selected data members of the class, and save them to or restore them from an object of the MFC class CArchive.

A CArchive object acts as an intermediary between the object to be serialized and the storage medium. Also, a CArchive object is always associated with a CFile object. The CFile object usually represents a disk file, but it can also represent a memory file. For example, you could associate a CArchive object with a CSharedFile object to serialize data to and from the Windows Clipboard. Additionally, a CArchive object provides a type-safe buffering mechanism for reading and writing serializable objects to and from a CFile object.

A given CArchive object is used either to store data or to load data, but never both. The life of a CArchive object is limited to one pass, through either writing objects to a file or reading objects from a file. Separately created CArchive objects are required to serialize data to a file, and also to restore data back from a file. The status of a CArchive object, whether used for storing or loading, can be determined by querying the Boolean return value of the CArchive::IsStoring() function.

The CArchive class defines both the insertion operator (<<) and the extraction operator (>>). These operators are used in a manner similar to the insertion and extraction operators defined for the standard C++ stream classes, as illustrated by the following code:

if (ar.IsStoring()) {      ar << m_string;      } else {      ar >> m_string;      }

You can use the insertion and extraction operators to store data to, and retrieve data from, a CArchive object. Table 6.2 lists the data types and objects that can be used with the insertion and extraction operators.

Table 6.2 Data Types and Objects Used with Insertion and Extraction Operators

CObject* SIZE and CSize float
WORD CString POINT and CPoint
DWORD BYTE RECT and CRect
double LONG CTime and CTimeSpan
int COleCurrencyCOleVariant
COleDateTime COleDateTimeSpan

Serialization Process

In Lesson 4 of Chapter 3, you learned that the application data is stored in the application's document object. Application data is serialized to a document file on disk, then restored from the document file into the document object. A document file type is associated with an application by specifying a filename extension in the Advanced Options dialog box, in Step 4 of the AppWizard (see Lesson 1 of Chapter 2).

The document object begins application data serialization in response to file commands selected by the user. A CArchive object of the appropriate type (according to whether data is to be saved to or restored from the archive) is created by the framework, and passed as a parameter to the document object's Serialize() function.

The AppWizard creates a stub Serialize() function for your document class. You must add code to this function to store or retrieve persistent data members to or from the archive. You can store and retrieve simple data members using the << and >> operators. If the document object contains more complex objects that implement their own serialization code, you must call the Serialize() function for those objects, and forward a reference to the current archive.

As an example, consider an application TestApp that maintains a document class with three data members, as shown by the following code sample:

Class CtestAppDoc {      CString m_string;      DWORD m_dwVar;      MyObj m_obj; }

Assume that the MyObj class is a serializable class.

The following code illustrates a Serialize() function that might be written for the TestApp document class:

void CTestAppDoc::Serialize(CArchive& ar) {      if (ar.IsStoring())      {           ar << m_string;           ar << m_dwVar;      }      else      {           ar >> m_string;           ar >> m_dwVar;      }      m_obj.Serialize(ar); }

Note how the MyObj::Serialize() function is called outside of the conditional branching code, as it contains its own branch condition to determine whether data is being stored or retrieved. Figure 6.1 illustrates how you can apply this technique to serialize objects recursively.

click to view at full size.

Figure 6.1 Serializing contained objects

As long as your serialization routines are kept consistent, complex object structures can be saved to disk and restored to an application. You need to ensure that the storing and restoring branches of your Serialize() functions match—in other words, that they store and restore the same objects in the same order.

The serialization routines handle proper reconstruction of an object structure when this structure is restored from a disk file. Serialization accomplishes this by writing information about the object's type as well as its state (data member values) into the disk file. When an object is restored, this information is used to determine what type of object needs to be created to receive the data. The serialization routine automatically performs the object creation. However, to ensure that this action works properly, you must provide a default constructor (one with no arguments) for your serializable class.

In the following practice exercise, you will learn the steps required to implement simple serialization of application data.

Serializing Application Data

In Lesson 1 of Chapter 5 you added two member variables, CMyAppDoc::m_nLines and CMyAppDoc::m_string, as application data for the MyApp application. You will now add code to this project to serialize these data items to a document file.

  • To serialize the MyApp application data
    1. Locate the Serialize() function created by the AppWizard for your document class CMyApp. The generated code for the Serialize() function is shown in the following code example:
    2. void CMyApp::Serialize(CArchive& ar) {      if (ar.IsStoring())      {           // TODO: add storing code here      }      else      {           // TODO: add loading code here      } }

    3. Replace the //TODO comments with code to store and restore the document data members. The completed code should look as follows:
    4. void CMyAppDoc::Serialize(CArchive& ar) {      if (ar.IsStoring())      {           ar << m_nLines;           ar << m_string;      }      else      {           ar >> m_nLines;           ar >> m_string;      } }

    5. Locate the CMyAppDoc::OnDataEdit() function. At the end of the function, directly beneath the call to UpdateAllViews(), add the following line:
    6. SetModifiedFlag();

      Thus, the entire function looks as follows:

      void CMyAppDoc::OnDataEdit() {      CEditDataDialog aDlg;      aDlg.m_nLines = m_nLines;      aDlg.m_strLineText = m_string;      if(aDlg.DoModal())      {           m_nLines = aDlg.m_nLines;           m_string = aDlg.m_strLineText;           UpdateAllViews(NULL);           SetModifiedFlag();      } }

      The CDocument::SetModifiedFlag() function is called to notify the application framework that the application data has been modified. This function causes the framework to prompt the user to save changes before closing a document.

    7. Use ClassWizard to overload the CDocument::DeleteContents() function for the CMyAppDoc class. Replace the comment code in the generated code with the following lines:
    8. m_nLines = 0; m_string = "";

      Thus, the entire function looks as follows:

      void CMyAppDoc::DeleteContents() {      m_nLines = 0;      m_string = "";      CDocument::DeleteContents(); }

    Unlike multiple-document interface (MDI) applications, which create a new document object each time a new document is created or an existing document file is opened, single-document interface (SDI) applications create only one document object, which is reused each time a document is created or opened. The DeleteContents() function clears the application data held in the document object before the object is reused. When developing an SDI application, you must implement a DeleteContents() function that sets all document object data members to zero or null values. Otherwise, you will find data from previous editing sessions in your current document.

  • To test MyApp serialization
    1. Build and run the MyApp application.
    2. Using the Edit option on the Data menu, type a string to display, and also type the number of times you want it displayed. Click OK to close the Edit Document Data dialog box.
    3. On the File menu, choose New. A message box appears, prompting you to save changes to your untitled MyApp document before opening the new document. This dialog box appears because your code called the SetModifiedFlag() function after you modified the application data.
    4. Click Yes. Use the Save As dialog box to save your document as MyFile.mya in the current directory.
    5. Another untitled document now appears on your screen. The DeleteContents() function that you provided has cleared the data from your MyFile.mya document.
    6. Open the MyFile.mya file by choosing Open from the File menu, or by selecting MyFile.mya from the Most Recently Used list on the File menu.
    7. Your application data is now restored and displayed as it originally was, both in the application window and in the Edit Document Data dialog box's edit controls.

    Making a Serializable Class

    You have already seen how a document object can contain objects that implement their own serialization code. To create a serializable class, perform the following steps:

    1. Derive the class from CObject or a class derived from CObject.
    2. Provide a default constructor (one with no arguments) for your class.
    3. Add the MFC macro DECLARE_SERIAL to the class declaration in the header file. DECLARE_SERIAL, and its partner IMPLEMENT_SERIAL, provide MFC run-time class information for your class. These macros also provide a global extraction operator (>>), which uses the run-time class information to restore objects of your class from an archive. DECLARE_SERIAL takes the name of your class as a parameter.
    4. The following code illustrates a serializable class declaration:

      // MyClass.h class CMyClass : public CObject   {      DECLARE_SERIAL(CMyClass) public:      CMyClass() {;}  // Default constructor      virtual void Serialize(CArchive& ar); };

    5. Add the IMPLEMENT_SERIAL macro to your class implementation (.cpp) file. The IMPLEMENT_SERIAL macro takes three parameters: the name of the class being serialized, the name of its parent class, and a schema number, as shown here:
    6. IMPLEMENT_SERIAL(CMyClass, CObject, 1)

      The schema number allows you to implement a versioning system for your document files. You will likely change your application documents' object structure between releases. These changes will most likely cause errors for a user that tries to use a new version of your application to open a document created with an older version.

      You can assign a different schema number to an object's IMPLEMENT_ SERIAL macro for each release that changes the object's structure. This action allows you to add code that detects discrepancies between application and document versions, and takes appropriate actions, such as displaying an error message or running a document format conversion routine.

    7. Provide an override of the CObject::Serialize() function for your class.

    Serializing MFC Collection Classes

    MFC's templated collection classes CArray, CList and CMap, implement their own versions of the Serialize() function that serialize all of the elements in the collection.

    Suppose your document class contains a collection of integer values as shown in the following code sample:

    CList<int, int &> m_intList;

    This collection can be serialized by adding the following line to the document's Serialize() function:

    m_intList.Serialize(ar);

    This line of code is all that is required to serialize a collection of simple types. CList::Serialize() calls the global helper function template SerializeElements(), which has the following signature:

    template<class TYPE> void AFXAPI SerializeElements(CArchive& ar, TYPE* pElements, int nCount);

    The compiler generates an appropriate instantiation of this template for the collection class element type. The SerializeElements() function's default behavior is to perform a bitwise copy of the data contained in the collection (referenced by the pointer pElements) to or from the archive.

    This default behavior is fine for simple objects, but is problematic for more complicated object structures. Suppose that your document class contains the member as shown in the following example code:

    CList<CMyClass, CMyClass &> m_objList;

    CMyClass would be defined as follows:

    class CmyClass {      DECLARE_SERIAL(CMyClass) public:      CMyClass() {;}      int m_int;      DWORD m_dw;      CString m_string;      virtual void Serialize(CArchive& ar); }

    Attempting to serialize the m_objList collection by adding the following line to the document object's Serialize() function will cause errors:

    m_objList.Serialize(ar);

    Such errors will result because the CMyClass objects contain CStrings; which are complex objects that use custom memory allocation and reference-counting techniques.

    The default SerializeElements() function generated for the m_objlist collection will attempt to read or write a bitwise copy of the collection elements to or from the archive, therefore bypassing the custom serialization routines built in the << and >> operators defined for the CString class.

    In this case, you must write your own version of the SerializeElements() function. Assuming that CMyClass has been properly constructed as a serializable class, the corresponding SerializeElements() function might look as follows:

    template <> void AFXAPI SerializeElements <CMyClass>      (CArchive& ar, CMyClass * pNewMC, int nCount) {      for (int i = 0; i < nCount; i++, pNewMC++)      {           // Serialize each CMyClass object           pNewMC->Serialize(ar);      } }

    NOTE
    You do not have to provide your own version of SerializeElements() for a simple collection of CString objects, as MFC provides one as part of the CArchive source code.

    Lesson Summary

    The MFC application framework implements a technology called serialization that enables you to preserve your application data's object structure when saving it to, and restoring it from, a persistent archive. All classes that implement serialization must be derived from the CObject class, and additionally must overload the CObject::Serialize() function.

    The Serialize() function stores and retrieves persistent data members to and from a CArchive class object. This object acts as an intermediary between the object to be serialized and the storage medium, which is usually a disk file encapsulated as a CFile class object. In addition, a CArchive object provides a type-safe buffering mechanism for writing and reading serializable objects to or from a CFile object. Separate CArchive objects must be used for storing and retrieving data. Once the archive object has been created, its role (determined by whether the CArchive::IsStoring() function returns TRUE or FALSE) cannot be changed.

    The document object begins application data serialization in response to user commands to load or save files. A CArchive object is created by the framework, and passed as a parameter to the document object's Serialize() function.

    The AppWizard creates a stub Serialize() function for your document class. You must add code to this function to store or retrieve persistent data members to, or from, the archive. The CArchive class defines the << insertion operator and the >> extraction operator, which can be used to store or retrieve various C++ and MFC data types. If an object contains other serializable objects, you call the Serialize() function for these objects. To make a class serializable, you must:

    • Derive the class from CObject.
    • Provide a default constructor for your class.
    • Add the DECLARE_SERIAL macro to the class declaration and the IMPLEMENT_SERIAL macro to your class implementation file.
    • Provide an override of the CObject::Serialize() function for your class.

    To serialize an instance of an MFC collection template class, simply call the collection object's serialize function. It is important to be aware that the collection template classes implement serialization by calling the instantiation of the SerializeElements() function template that is generated for the element type of the collection class. The default behavior of the SerializeElements() function is to perform a bitwise copy of the data contained in the collection to or from the archive. If this behavior is not appropriate for the element type of your collection, you should provide your own implementation of the SerializeElements() function template.



    Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
    Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
    ISBN: 0735607958
    EAN: 2147483647
    Year: 1999
    Pages: 95

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net