Free VC++ Tutorial

Web based School

Previous Page Main Page Next Page


18 — Microsoft Foundation Classes: An Overview

The Microsoft Foundation Classes Library is arguably the most distinguishing component of the Visual C++ development system. This vast collection of C++ classes encapsulates most of the Win32 API and provides a powerful framework for typical (and not so typical) applications.

MFC and Applications

A typical MFC application is one that is created using the Visual C++ AppWizard. However, it is not necessary to use the AppWizard to create an MFC application, nor is the use of MFC restricted to such AppWizard-generated programs. Many simple MFC classes can be used in simple programs including, to the surprise of many programmers, command-line (console) applications.

In fact, consider the simple program shown in Listing 18.1. This MFC program can be compiled from the command line (cl /MT hellomfc.cpp).

    Listing 18.1: A simple MFC console application.
#include <afx.h>

CFile& operator<<(CFile& file, const CString& string)

{

    file.Write(string, string.GetLength());

    return file;

}

void main(void)

{

        CFile file((int)GetStdHandle(STD_OUTPUT_HANDLE));

        CString string = "Hello, MFC!";

        file << string;

}

That said, the primary goal of the MFC is to provide an encapsulation for the Windows API. Its major classes, such as CWnd, CDialog, or CGdiObject, represent the results of this design philosophy. Ideally, an MFC application never has to call Windows API functions directly; instead, it constructs an object of the appropriate type and utilizes the object's member functions. The object's constructor and destructor take care of any initialization and cleanup that is necessary.

For example, an application that needs to draw into a window can do so by constructing a CClientDC object and calling the object's member functions (which closely map GDI drawing functions). The CClientDC constructor makes the appropriate calls to create a device context, set up the mapping mode, and perform other initializations. When the object goes out of scope or is destroyed using the delete operator, the destructor automatically releases the device context. This kind of encapsulation would make writing application programs easier even without the benefit of the Developer Studio, its AppWizard, and other powerful features.

The problem many programmers new to MFC must face is the steep learning curve. I remember well that when I first got my hands on MFC 1.0 (which was, needless to say, significantly less complex than MFC 4 is today), I felt at first completely overwhelmed. It seemed that to accomplish even the simplest task took days of browsing through the thick paper manuals. True, it is simple to write a few lines of code like this:

CClientDC *pDC;

pDC = new CClientDC(this);

pDC->Rectangle(0, 0, 100, 100);

delete pDC;

but only if you know exactly what you are doing! Otherwise, you have to first find out that there is indeed a class that encapsulates the functionality of a device context associated with a window's client area. Next, you must explore the member functions of the CClientDC class and its parent classes, to find out that there is indeed a CDC::Rectangle member function. (After all, a different design approach might have placed such a member function into the CWnd class.) Third, you may wish to double-check to ensure that no other initialization work is needed. With paper manuals and no other guidance, these steps may consume many valuable hours.

That said, the programmer today is not without guidance anymore. Apart from this splendid guide you are holding in your hands, there are online references, help files, excellent tutorials, valuable example programs, and most importantly, the AppWizard.

I always found that a high-level overview, "getting the big picture," helps tremendously when trying to understand a complex subject. So before we get bogged down in the details, allow me to devote the rest of this chapter to presenting just such an overview of MFC.

Foundation Class Fundamentals

The classes in MFC are loosely organized into several major categories. Of these, the two major categories are Application Architecture classes and Window Support classes. Other categories contain classes that encapsulate a variety of system, GDI, or miscellaneous services.

Most classes in MFC are derived from a common root, the CObject class. The CObject class implements two important features: serialization and run-time type information. (Note that the CObject class predates RTTI, the new C++ run-time type information mechanism; the CObject class does not use RTTI.) However, there are several simple support classes that are not derived from CObject.

The major MFC categories are illustrated in Figure 18.1.


Figure 18.1. Overview of MFC.

Because of the importance of CObject, we'll take a look at that class first.

The CObject Class and Serialization

As I mentioned, the CObject class, the "mother of all classes" (well, almost) implements serialization and run-time type information. But what do these concepts mean?

Serialization is the conversion of an object to and from a persistent form. Or, in simpler terms, it means writing an object to disk or reading it from the disk or any other forms of persistent storage.

Why is serialization necessary? Why not just write

cout << myObject;

and get it over with? For one thing, everybody knows that writing anything to disk that involves pointers can be tricky. When you later read that disk file, chances are that whatever your pointer pointed to has either been moved or is no longer present in memory at all. But this is not the end of the story.

MFC objects are not only written to disk files. Serialization is also used to place an object on the clipboard or to prepare the object for OLE embedding.

The MFC Library uses CArchive objects for serialization. A CArchive object represents persistent storage of some kind. When an object is about to be serialized, CArchive calls the object's Serialize member function, one of the overridable functions in CObject. Thus, the underlying philosophy is that it is the object that knows best how to prepare itself for persistent storage, while it is the CArchive object that knows how to transfer the resulting data stream to persistent media.

But let an example do the talking. This example implements something simple, a string class CMyString. (Note that this has nothing to do with the sophisticated MFC CString class; the sole purpose of this exercise is to demonstrate CObject serialization.)

CMyString has two data members; one represents the length of the string, the other is a pointer to the string data. Unlike C strings, a CMyString can contain embedded null characters and does not require a terminating null character. The declaration of the CMyString class would thus look like this (only the data members and the Serialize member function are shown):

class CMyString

{

private:

    WORD m_nLength;

    LPSTR m_pString;

public:

    virtual void Serialize(CArchive &ar);

};

Why am I using the Windows type WORD instead of declaring m_nLength an integer? There is a very important reason. Windows guarantees that the WORD type will represent a 16-bit integer on all present and future versions of Windows. This is important when it comes to storing data on persistent storage; it ensures that data files written under one operating system specific version of our application remain readable under another. Had we used int instead, we would be facing the problem that an int is a 16-bit type under Windows 3.1, a 32-bit type under Windows 95 and Windows NT, and who knows what under future versions of Windows. Thus, data files created under these different operating systems would be incompatible.

The Serialize member function is responsible for actually writing data to, and reading data from, a CArchive object. However, we cannot simply just write m_nLength and m_pString to the archive. Instead, we have to write the data m_pString points to, that is, the string itself. When it comes to reading the data, we must first determine the length of the string, allocate memory for it, and then read the string itself:

CMyString::Serialize(CArchive &ar)

{

    if (ar.IsStoring())

    {

        ar << m_nLength;

        ar.Write(m_pString, m_nLength);

    }

    else

    {

        ar >> m_nLength;

        m_pString = new char[m_nLength];

        ar.Read(m_pString, m_nLength);

    }

}

In order for this code to compile and run correctly, it is also necessary to use a few helper macros. For a class to be serializable, one must use the DECLARE_SERIAL macro in the class declaration and the IMPLEMENT_SERIAL macro somewhere in the class's implementation file. One specific feature that these macros add to a class is MFC run-time type information.

Why is type information necessary for successful serialization? Well, consider what happens when data is read from persistent storage. Before reading an object, we know nothing about it other than the fact that it is CObject derived. Run-time type information that has been serialized together with the object helps to determine the actual type of the object. Once type information has been obtained, the CArchive object can create an object of the new type and call its Serialize member function to read in object-specific data. Without run-time type information this would not be possible.

Run-Time Type Information

MFC maintains run-time type information with the help of the CRuntimeClass class and several helper macros.

The CRuntimeClass class has member variables holding the name of the class and the size of an object belonging to that class. This information not only identifies the class but also assists in serialization.

Applications rarely use CRuntimeClass directly. Instead, they rely on a series of macros that embed a CRuntimeClass object in the declaration of a CObject-derived class, and provide an implementation.

There are three pairs of macros, summarized in Table 18.1.

    Table 18.1. Helper macros.
Symbolic constant


Description


DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC

Adds run-time information to the class. Enables the use of the IsKindOf member function.

DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE

Renders the class dynamically creatable through CRuntimeClass::CreateObject.

DECLARE_SERIAL and IMPLEMENT_SERIAL

Adds serialization capability to the class; enables the use of << and >> operators with a Carchive.

You only need to use one set of these macros at any time. The functionality of DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE is a superset of the functionality of DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC; and the functionality of DECLARE_SERIAL/IMPLEMENT_SERIAL is a superset of the functionality of DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC.

To use these macros, you embed the DECLARE_ macro in the declaration of your class and you add the IMPLEMENT_ macro to your implementation file. To create a CMyString class that is CObject-derived and supports serialization, you would therefore declare the class as follows:

class CMyString : public CObject

{

    DECLARE_SERIAL(CMyString)

    ...

};

In the implementation file, you would add the following macro (outside any member functions):

IMPLEMENT_SERIAL(CMyString, CObject, 0)

MFC and Multiple Inheritance

A frequently asked question with respect to MFC concerns the use of the classes of MFC with multiple inheritance. Generally, the answer is that although using multiple inheritance with MFC is possible, doing so is not recommended.

In particular, the CRuntimeClass class does not support multiple inheritance. As CRuntimeClass is used by CObject for run-time class information, dynamic object creation, and serialization, this limitation has a serious effect on any attempt to use multiple inheritance in an MFC program.

If your project requires the use of multiple inheritance with MFC, I recommend MFC Technical Note 16 (Using C++ Multiple Inheritance with MFC), supplied as part of the Visual C++ on-line documentation. This technical note provides an excellent in-depth review of the issues concerning MFC and multiple inheritance.

MFC and Windows Objects

Many MFC classes represent objects in Windows, such as a window, a device context, or a GDI object. It is important to realize that an object of such an MFC class (for example, a CWnd object) is not the same as the Windows object. The CWnd object only represents a window; the same goes for other MFC classes. The existence of a Windows object does not automatically imply the existence of a corresponding MFC object, nor does the existence of an MFC object automatically imply that a corresponding Windows object also exists. In many situations, an unattached MFC object is created, only to be attached later to an existing or newly created Windows object. In other situations, temporary MFC objects are created to briefly represent long-lived Windows objects (for example, a temporary CWnd object may be used to represent the desktop window).

Window Support Classes

Window Support classes provide encapsulation for common types of windows. These include frame and view windows, dialog windows, and controls. All window support classes are derived from the class CWnd, which itself is derived from CObject. The CWnd class encapsulates the functionality common to all windows. Its large number of member functions can be organized into several categories, which are summarized in Table 18.2.

    Table 18.2. CWnd Member function categories.
Category


Description


Initialization

Window initialization and creation

Window state functions

Set or retrieve window settings

Size and position

Retrieve or change size and position

Window access

Window identification

Update and painting

Drawing functions

Coordinate mapping

Mapping between logical and physical coordinates

Window text

Manipulate window text or alter its appearance

Scrolling

Manipulate scrollbars

Drag and drop

Accept drag and drop files

Caret

Manipulate the caret

Dialog box

Manipulate dialog box items

Menu

Manipulate menus

Tooltips

Manipulate tooltips

Timer

Set and kill timers

Alert

Window flashing and message boxes

Window message

Manage messages

Clipboard

Manipulate clipboard contents

OLE controls

Handle OLE controls

Overridables

Handle messages and other conditions

Frame Windows

Frame windows are typically used in the context of MFC framework applications. They encapsulate the functionality of the application's main window, and manage the application's menu bar, toolbar buttons, and status bar.

The different types of frame windows are shown in Figure 18.2. They are used in the context of SDI and MDI applications and OLE in-place editing as appropriate. All frame windows are derived from the CFrameWnd class, itself a descendant of CWnd.


Figure 18.2. Frame window classes.

These frame window classes are typically used as the base classes for user-defined frame window classes.

Closely related to frame windows are control bars such as toolbars and status bars. Control bar classes are derived from CControlBar, which in turn is derived from CWnd. These classes are shown in Figure 18.3.


Figure 18.3. Control bar classes.

One additional class, CSplitterWnd, is used to create splitter windows, windows that have multiple panes. Typical use of CSplitterWnd involves embedding a CSplitterWnd object inside a frame window object.

View Windows

View windows are also specific to the MFC framework. An MFC application uses view windows to present the contents of its document to the user for interaction.

There are several view window types representing the different forms in which a document's view can be presented. View window classes exist that support scrolling, text editing, list and tree controls, and dialog-like forms.

All view window classes are derived from the CView class, which is a descendant of CWnd. The hierarchy of view window classes is shown in Figure 18.4.


Figure 18.4. View window classes.

Like frame window classes, view window classes also typically serve as base classes for user-defined classes that implement application-specific view functionality.

Dialogs

Dialog classes encapsulate the functionality of both user-defined and common dialogs. The hierarchy of dialog classes is shown in Figure 18.5.


Figure 18.5. Dialog classes.

Dialog classes can be used outside MFC framework applications. For example, the program in Listing 18.2 displays a color selection common dialog using the CColorDialog class. You can compile this program from the command line by typing cl /MT colors.cpp.

    Listing 18.2. Using an MFC dialog class in a non-MFC application.
#include <afx.h>

#include <afxdlgs.h>

int WINAPI WinMain(HINSTANCE d1, HINSTANCE d2, LPSTR d3, int d4)

{

    CColorDialog dlg;

    dlg.DoModal();

    return 0;

}

Controls

Control classes encapsulate the functionality of standard Windows controls, Windows 95 common controls, and OLE controls (OCXs). The hierarchy of control classes is shown in Figure 18.6.


Figure 18.6. Control classes.

Application Architecture Classes

Application architecture classes are all derived from the base class CCmdTarget. A CCmdTarget object is an object that has a message map and can process messages. As windows are obvious recipients of messages, the CWnd class is also derived from CCmdTarget.

Application architecture classes include document classes, document template classes, document item classes, application object classes, and several OLE related classes.

Document classes

Documents are entities that represent a unit of data that the user opens and manipulates. Document objects usually cooperate closely with corresponding view objects that handle presentation of the data and user interaction. The hierarchy of document classes is shown in Figure 18.7.


Figure 18.7. Document classes.

Document Templates

Document templates describe the basic behavior of user-defined document and view classes. The family of document template classes is illustrated in Figure 18.8.


Figure 18.8. Document template classes.

Application Objects

Application object classes represent threads and processes (Figure 18.9).


Figure 18.9. Application object classes.

Every MFC framework application has a CWinApp-derived object, which supplies your application's main message loop.

Document Items

Document items are objects that comprise a document. For example, the document of a drawing application may consist of objects that represent drawing shapes. MFC uses its document item classes for OLE server and client items. The hierarchy of document item classes is shown in Figure 18.10.


Figure 18.10. Document item classes.

Other Application Architecture Classes

Several other Application Architecture classes exist that contribute to the implementation of OLE within the MFC framework. These classes are shown in Figure 18.11.


Figure 18.11. OLE-related Application Architecture classes.

Miscellaneous Classes

For lack of a better term, I called class families that support system and graphic services, collections, and other CObject-derived classes miscellaneous classes.

Graphic Support Classes

GDI functionality is supported by device-context classes and GDI object classes, which are illustrated in Figure 18.12. Both class families are derived from CObject.


Figure 18.12. Graphic support classes.

System Support Classes

System support classes encapsulate the functionality of system objects such as exceptions, synchronization objects, and files. Other system support classes provide support for ODBC, DAO, and WinSock. The hierarchy of these classes is shown in Figure 18.13.


Figure 18.13. System support classes.

Collection Classes

Collection classes include arrays, lists, and maps. Arrays are dynamically allocated collections of objects organized by an integer index. Lists are ordered collections. Maps are collections organized by a key.

The hierarchy of collection classes is shown in Figure 18.14.


Figure 18.14. Collection classes.

Non-CObject-derived Classes

The MFC also contains several support classes that are not derived from the CObject class. These include simple value types (for example, CRect or CString), typed template collections, and many other support classes. Non CObject-derived classes are shown in Figure 18.15.


Figure 18.15. Non-CObject-derived classes.

Summary

The MFC Library represents a powerful framework for constructing Windows applications. Classes in MFC encapsulate most Windows functionality, including functionality related to applications, threads, windows, dialogs, controls, graphic objects, device contexts, and much, much more. However, the use of MFC is not restricted to so-called MFC framework applications; other Windows programs and even some console applications can benefit from this library.

The root of most MFC classes is the CObject class. This class implements run-time-type checking (distinct from the new C++ RTTI feature) and serialization. Serialization is a powerful, platform-independent mechanism for creating an image of an object on persistent storage and loading object data from such storage to memory. Serialization is not limited to disk files; it is also used in clipboard transfers and OLE.

The major MFC categories include Application Architecture classes, Window Support classes, and other classes that encapsulate system, GDI, and miscellaneous services.

Window support classes correspond to various window types used by the system or provided by the MFC Library. These include frame and view windows, dialogs, and controls. All such classes are derived from the CWnd class, which encapsulates basic functionality common to all windows.

CWnd itself is derived from the CCmdTarget class, which is the base class for all classes that have message maps and can handle and route messages. All Application Architecture classes are also derived from CCmdTarget. These include classes for documents, document templates, document items, OLE functionality, and thread and process objects. The latter type is called CWinApp; every MFC framework application contains a CWinApp-derived object, which implements the application's main message loop.

Previous Page Main Page Next Page