- Table of Contents
- Copyright
- About the Authors
- About the Contributors
- Acknowledgments
- Tell Us What You Think!
- Introduction
- How to Use This Book
- What You Need to Use This Book
- What's New in Visual C++ 6.0
- Contacting the Main Author
- Part I: Introduction
- Chapter 1. The Visual C++ 6.0 Environment
- Part II: MFC Programming
- Chapter 2. MFC Class Library Overview
- Chapter 3. MFC Message Handling Mechanism
- Chapter 4. The Document View Architecture
- Chapter 5. Creating and Using Dialog Boxes
- Chapter 6. Working with Device Contexts and GDI Objects
- Chapter 7. Creating and Using Property Sheets
- Chapter 8. Working with the File System
- Chapter 9. Using Serialization with File and Archive Objects
- Part III: Internet Programming with MFC
- Chapter 10. MFC and the Internet Server API (ISAPI)
- Chapter 11. The WinInet API
- Chapter 12. MFC HTML Support
- Part IV: Advanced Programming Topics
- Chapter 13. Using the Standard C++ Library
- Chapter 14. Error Detection and Exception Handling Techniques
- Chapter 15. Debugging and Profiling Strategies
- Chapter 16. Multithreading
- Chapter 17. Using Scripting and Other Tools to Automate the Visual C++ IDE
- Part V: Database Programming
- Chapter 18. Creating Custom AppWizards
- Chapter 19. Database Overview
- Chapter 20. ODBC Programming
- Chapter 21. MFC Database Classes
- Chapter 22. Using OLE DB
- Chapter 23. Programming with ADO
- Part VI: MFC Support for COM and ActiveX
- Chapter 24. Overview of COM and Active Technologies
- Chapter 25. Active Documents
- Chapter 26. Active Containers
- Chapter 27. Active Servers
- Chapter 28. ActiveX Controls
- Part VII: Using the Active Template Library
- Chapter 29. ATL Architecture
- Chapter 30. Creating COM Objects Using ATL
- Chapter 31. Creating ActiveX Controls Using ATL
- Chapter 32. Using ATL to Create MTS and COM+ Components
- Part VIII: Finishing Touches
- Chapter 33. Adding Windows Help
- Part IX: Appendix
The CObject Class
At least as far as MFC is concerned, CObject is the mother of all classes (well, most of them anyway). Almost all the classes in MFC are derived from CObject—with a few notable exceptions, such as CString. Deriving a class from CObject provides several very important features, including serialization, runtime type information, and some very important debugging features.
Serialization
Many features of Windows programming with MFC require the capability of serializing the data in your objects. Perhaps the simplest example of this is saving an object to a file. You need to have a way to convert your object to a series of bytes that can be written to disk and brought back later to restore your object to its previous state.
To implement serialization in your classes, you first derive them, either directly or indirectly, from CObject. Then you can implement the Serialize member function for your class to serialize its data. To see just how to do this, let's start by looking at a few macros MFC provides to help.
The DECLARE_SERIAL and IMPLEMENT_SERIAL Macros
To help implement serialization in your class, MFC provides a pair of macros: DECLARE_SERIAL for use in your class declaration (usually in an h file), and IMPLEMENT_SERIAL for use in your class implementation (a cpp file).
The DECLARE_SERIAL macro takes only one parameter: the name of your class. Placing this in your class declaration provides prototypes for the serialization functions and some special handling for the insertion operator. You can see how this is used in the following class declaration:
Class CEmployee : public CObject
{
public:
DECLARE_SERIAL(CEmployee)
void Serialize(CArchive& ar);
private:
int m_EmpNo;
CString m_Name;
float m_Salary;
};
Serialize()
Notice that this example declares a Serialize() function, which takes as a parameter a reference to a CArchive object, which provides a context for the serialization. Before calling the Serialize() function, the MFC framework has prepared the CArchive object either to read from or write to objects of your class. You must implement the specific behavior for the Serialize() function in each class that you intend to serialize.
As mentioned previously, the same Serialize() function implements both loading and storing, based on the CArchive context. You can use the CArchive::IsLoading() or CArchive::IsStoring() function to determine the direction of serialization. The implementation for the CEmployee class declared earlier might look like this:
IMPLEMENT_SERIAL(CEmployee, CObject, 0x200)
void CEmployee::Serialize(CArchive& ar)
{
// call base class Serialize() first
CObject::Serialize(ar);
// then serialize the data for this class
if(ar.IsLoading())
{
ar >> m_EmpNo;
ar >> m_Name;
ar >> m_Salary;
}
else
{
ar << m_EmpNo;
ar << m_Name;
ar << m_Salary;
}
} // end CEmployee::Serialize
There are many interesting things that you should notice in this example, beginning with the use of the IMPLEMENT_SERIAL macro, which takes three parameters: the class name, the base class it is derived from, and a schema number, which you will learn about in just a bit.
Next, you should notice that you call the Serialize member of the base class. Every implementation of Serialize() must call the Serialize() function of the base class to allow it to serialize its data first, before you serialize the data for your class.
Serialization Operators
Notice that the serialization is performed by the overloaded insertion and extraction operators. These are predefined for the CArchive class for the following data types:
- BYTE
- WORD
- DWORD
- LONG
- double
- float
- CObject*
The insertion and extraction operators are also defined for any class that implements serialization. You can thank the DECLARE_SERIAL and IMPLEMENT_SERIAL macros for this. If you need to use any other data types, you have to create your own override functions or use macros or type casts to use the supported types.
Serializing Different Versions
Earlier, you learned that the IMPLEMENT_SERIAL macro takes a schema number for its third parameter. This can be any number in the valid range of type UINT, with the exception of -1, which is reserved for use by MFC. The schema number effectively allows you to embed a version number in your serialized data; if the schema number you specify in IMPLEMENT_SERIAL does not match the schema number in the file you are reading, MFC will fail when attempting to read the file. In debug builds, MFC will display a failed assertion dialog box.
If MFC just fails when presented with out-of-date file information, how can it support multiple versions? This is where the VERSIONABLE_SCHEMA macro comes in. If you combine your current schema number and the VERSIONABLE_SCHEMA macro by using the OR operator (|), your Serialize() routine will write your data with the current schema number, but can read any schema. This is handled by use of the CArchive::GetObjectSchema() function, as you will see in the following example. Here, you assume that the previous version of CEmployee did not implement the m_Name member:
IMPLEMENT_SERIAL(CEmployee, CObject, VERSIONABLE_SCHEMA|0x200)
void CEmployee::Serialize(CArchive& ar)
{
// first, call base class Serialize function
CObject::Serialize(ar);
// Now we do our stuff
if(ar.IsStoring())
{
// We are writing our class data,
// so we don't care about the schema
ar << m_EmpNo;
ar << m_Name;
ar << m_Salary;
}
else
{
// we are loading, so check the schema
UINT nSchema = ar.GetObjectSchema();
switch(nSchema)
{
case 0x100:
// Old schema, default m_Name
ar >> m_EmpNo;
m_Name = ""Dilbert";
ar >> m_Salary;
break;
case 0x200:
// current version
ar >> m_EmpNo;
ar >> m_Name;
ar >> m_Salary;
break;
default:
// Unknown Version, do nothing
break;
} // end switch
} // end if
} // end CCLient::Serialize()
As you can see, you should provide reasonable defaults for data that cannot be retrieved from the archive. On the other hand, you probably should provide some sort of mechanism to report unknown cases to the user, instead of doing nothing, as I did here.
Runtime Type Information | Next Section

Account Sign In
View your cart