MFC Class Library Overview
MFC Class Library Overview
The application framework helps get your application running by providing program
initialization, passing Windows messages to the appropriate places, and cleaning it all up
when your application exits. The framework also provides a solid foundation for more
advanced features, such as ActiveX and document view processing.
As you may have already noticed, MFC AppWizard has created a handful of classes for
you, based on your application's name. These fall into the basic classes of Application
classes, Document classes, View classes, and Frame classes. Each class is based on one
of the classes in the application architecture hierarchy
The first line shown here is what gives your application the real power of the MFC
application framework. This is where the CHiMomApp class is derived from CWinApp.
Apart from that, you will notice an awful lot of strange comments. These comments give
the ClassWizard landmarks to find the pieces of the code that it manipulates.
When your application first starts, it has one—and only one—thread, known as the
primary thread. This thread is encapsulated by the CWinApp class that you have derived.
This means that a pointer to your CWinApp object is also a pointer to the CWinThread
object for the primary thread. Once your application gets going, it can create as many
new threads as it wants to manage various tasks.
Note
Even though you may not be using more than one thread, the MFC libraries always
expect to link with multithreaded runtime libraries. You should select the appropriate
multithreaded libraries in the Build Settings under the C/C++ Code Generation options.
(This is not the default for new application workspaces that are not created with MFC
AppWizard.)
Windows programs are based on an event-driven model. This means that they run in the
traditional sense for only a short time at startup, then spend the rest of their lives waiting
around for messages, reacting to them, and waiting again for more messages. These
messages can be generated by simply moving the mouse, clicking on a button, or
selecting a menu command.
In C programs, these messages were generally handled by large switch blocks involving
case statements for each message that your application wanted to process. Because the
processing of these messages was often dependent on several other variables, most
applications ended up with a massive web of nested switch and if blocks.
To remedy this situation, and to allow you to use the power of C++ freely, MFC has
implemented message maps to allow your classes to handle Windows messages in a
much cleaner fashion. Any class derived from CCmdTarget may have its own message
map, allowing each class to handle the messages it is interested in however it chooses,
while leaving other messages to be handled higher in the class hierarchy.
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
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:
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:
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.
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:
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.