Appendix A

Windows Programming Review and a Look Inside CWnd


The Microsoft Foundation Classes were written for one single purpose: to make Windows programming easier by providing classes with methods and data that handle tasks common to all Windows programs. The classes that are in MFC are designed to be useful to a Windows programmer specifically. The methods within each class perform tasks that Windows programmers often need to perform. Many of the classes have a close correspondence to structures and "window classes" in the old Windows sense of the word class. Many of the methods correspond closely to API (Application Programming Interface) functions that are already familiar to Windows programmers.

Programming for Windows

If you've programmed for Windows in C, you know that the word class was used to describe the definition of a window long before C++ programming came to Windows. A window class is vital to any Windows C program. A standard structure holds the data that describes this window class, and a number of standard window classes are provided by the operating system. A programmer usually builds a new window class for each program and registers it by calling an API function, RegisterClass(). Windows that appear on the screen can then be created, based on that class, by calling another API function, CreateWindow().

A C-style windows class

The WNDCLASS structure, which describes the window class, is equivalent to the WNDCLASSA structure, which looks like this:

Listing A.1 WNDCLASSA structure from WINUSER.H

typedef struct 
tagWNDCLASSA {
    UINT        style;
    WNDPROC     
lpfnWndProc;
    int         cbClsExtra;
    
int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      
lpszMenuName;
    LPCSTR      lpszClassName;
} WNDCLASSA, *PWNDCLASSA, 
NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;

WINUSER.H sets up two very similar window class structures, WNDCLASSA for programs that use normal strings, and WNDCLASSW for Unicode programs. Unicode programs are covered in Chapter 28, "Future Explorations," in the "Unicode" section.

WINUSER.H is code supplied with Developer Studio. It's typically in the folder \Program Files\DevStudio\VC\include.

If you were creating a Windows program in C, you would need to fill a WNDCLASS structure. The members of the WNDCLASS structure are as follows:

Window Creation

If you've never written a Windows program before, you might be quite intimidated by having to fill out a WNDCLASS structure. But this is the first step in Windows programming in C. However, you can always find simple sample programs to copy, like this one:

WNDCLASS wcInit;
 wcInit.style = 0;
 wcInit.lpfnWndProc = 

(WNDPROC)MainWndProc;
 wcInit.cbClsExtra = 0;
 wcInit.cbWndExtra = 0;
 wcInit.hInstance = hInstance;
 wcInit.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(ID_ICON));
 wcInit.hCursor = LoadCursor (NULL, IDC_ARROW);
 wcInit.hbrBackground = GetStockObject 

(WHITE_BRUSH);
 wcInit.lpszMenuName = "DEMO";
 wcInit.lpszClassName ="NewWClass";
 return (RegisterClass 

(&wcInit));

Hungarian Notation

You might wonder what kind of variable name lpszClassName is. You also might wonder why it is wcInit and not just Init. Because Microsoft programmers use a variable naming convention called Hungarian Notation. It is so named because it was popularized at Microsoft by a Hungarian programmer named Charles Simonyi, and probably because at first glance, the variable names seem to be written in another language.

In Hungarian Notation, the variable is given a descriptive name, like Count or ClassName, that starts with a capital letter. If it is a multi-word name, each word is capitalized. Then, before the descriptive name, letters are added to indicate the type of the variableó for example, nCount for an integer, or bFlag for a Boolean (True or False) variable. In this way the programmer should never forget a variable type, or do something foolish like passing a signed variable to a function that is expecting an unsigned value.

The style has gained widespread popularity, though some people hate it. If you long for the good old days of arguing where to put the brace brackets, or better still whether to call them brace, face, or squiggle brackets, but can't find anyone to rehash those old wars any more, you can probably find somebody to argue about Hungarian Notation instead. The arguments in favor boil down to "you catch yourself making stupid mistakes" and the arguments against to "it's ugly and hard to read." But the practical truth is that the structures used by the API and the classes defined in MFC all use Hungarian Notation, so you might as well get used to it. You'll probably find yourself doing it for your own variables too. The prefixes are as follows:

Prefix Variable Type Comment
a Array
b Boolean
d Double
h Handle
i Integer "index into"
l Long
lp Long pointer to
lpfn Long pointer to function
m_ Member variable
n Integer "number of"
p Pointer to
s String
sz Zero terminated string
u Unsigned integer
C Class

Many people add their own type conventions to variable names; the wc in wcInit stands for window class.

Filling the wcInit structure and calling RegisterClass is fairly standard stuff, registering a class called NewWClass with a menu called DEMO and a WindProc called MainWndProc. Everything else about it is ordinary to an experienced Windows C programmer. After registering the class, when those old-time Windows programmers wanted to create a window on the screen, out popped some code like this:

 HWND hWnd;
 hInst = hInstance;
 hWnd = CreateWindow 
(
 "NewWClass",
 "Demo 1",
 WS_OVERLAPPEDWINDOW,
 
CW_USEDEFAULT,
 CW_USEDEFAULT,
 CW_USEDEFAULT,
 CW_USEDEFAULT,
 NULL,
 NULL,
 hInstance,
 NULL);
 if (! hWnd)
 return (FALSE);
 ShowWindow (hWnd, nCmdShow);
 UpdateWindow (hWnd);

This code calls CreateWindow(), then ShowWindow(), and UpdateWindow(). The parameters to the API function CreateWindow() are as follows:

CreateWindow() returns a window handleóeverybody calls their window handles hWndóand this handle is used in the rest of the standard code. If it's NULL, the window creation failed. If the handle returned has any non-NULL value, the creation succeeded and the handle is passed to ShowWindow() and UpdateWindow(), which together draw the actual window on the screen.

Handles

A handle is more than just a pointer. Windows programs refer to resources like windows, icons, cursors, and so on with a handle. Behind the scenes there is a handle table that tracks the address of the resource as well as information about the resource type. It's called a handle because a program uses it as a way to "get hold of" a resource. Handles are typically passed around to functions that need to use resources, and returned from functions that allocate resources.

There are a number of basic types of handles: HWND for a window handle, HICON for an icon handle, and so on. No matter what kind of handle is being used, remember it's a way to reach a resource so that you can use it.

Encapsulating the Windows API

API functions create and manipulate windows on the screen, handle drawing, connect programs to Help files, facilitate threading, manage memory, and much more. When these functions are encapsulated into MFC classes, your programs can accomplish these same basic Windows tasks, with less work on your part.

There are literally thousands of API functions, and it can take six months to a year to get a good handle on the API, so this book does not attempt to present a mini-tutorial on the API. In the "Programming for Windows" section earlier in this chapter you were reminded about two API functions, RegisterClass() and CreateWindow(). These form a good illustration of what was difficult about C Windows programming with the API, and how the MFC classes make it easier. Documentation on the API functions is available from inside Visual C++: click the InfoViewer tab in the Workspace pane, and expand the Platform SDK topic. Within that topic, expand Reference, Functions, and finally Win32 Functions to display a list of alphabetical categories such as ArrangeIconicWindows to CloseClipboard. Functions are arranged alphabetically within these categories. There are also index entries that will lead to specific functions.

Inside CWnd

CWnd is a hugely important MFC class. Roughly a third of all the MFC classes use it as a base classóclasses like CDialog, CEditView, CButton, and many more. It serves as a wrapper for the old style windows class and the API functions that create and manipulate window classes. For example, the only public member variable is m_hWnd, the member variable that stores the window handle. This variable is set by the member function CWnd::Create() and used by almost all the other member functions when they call their associated API functions.

You might think that the call to the API function CreateWindow() would be handled automatically in the CWnd constructor, CWnd::CWnd, so that when the constructor is called to initialize a CWnd object the corresponding window on the screen is created. This would save you, the programmer, a good deal of effort, because you can't forget to call a constructor. In fact, that's not what Microsoft has chosen to do. The constructor looks like this:

CWnd::CWnd()
{
 AFX_ZERO_INIT_OBJECT(CCmdTarget);
}

AFX_ZERO_INIT_OBJECT is just a macro, expanded by the C++ compiler's preprocessor, that uses the C function memset to zero out every byte of every member variable in the object, like this:

#define 
AFX_ZERO_INIT_OBJECT(base_class) [ccc]
 memset(((base_class*)this)+1, 0, sizeof(*this) [ccc]
 - sizeof(class base_class));

The reason why Microsoft chose not to call CreateWindow() in the constructor is that constructors cannot return a value. If something goes wrong with the window creation, there are no elegant or neat ways to deal with it. Instead, the constructor does almost nothing, a step that essentially cannot fail, and the call to CreateWindow() is done from within the member function Cwnd::Create(), or the closely related CWnd::CreateEx(), which looks like the one in Listing A.2.:

Listing A.2 CWnd::CreateEx() from WINCORE.CPP

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
 LPCTSTR lpszWindowName, DWORD dwStyle,
 int x, int y, int nWidth, int nHeight,
 HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
 // allow 
modification of several common create parameters
 CREATESTRUCT cs;
 cs.dwExStyle = 
dwExStyle;
 cs.lpszClass = 
lpszClassName;
 cs.lpszName = lpszWindowName;
 cs.style = dwStyle;
 
cs.x = x;
 cs.y = 
y;
 cs.cx = nWidth;
 cs.cy = nHeight;
 cs.hwndParent = 
hWndParent;
 cs.hMenu = 
nIDorHMenu;
 cs.hInstance = AfxGetInstanceHandle();
 cs.lpCreateParams = lpParam;
 if (!PreCreateWindow(cs))
 {
 PostNcDestroy();
 return FALSE;
 }
 
AfxHookWindowCreate(this);
 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
 cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
 cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
#ifdef _DEBUG
 if (hWnd == NULL)
 {
 TRACE1("Warning: Window creation failed: [ccc]
 GetLastError returns 0x%8.8X\n",
 GetLastError());
 }
#endif
 if (!AfxUnhookWindowCreate())
 PostNcDestroy();
 // cleanup if 

CreateWindowEx fails too soon
 if (hWnd == NULL)
 return FALSE;
 ASSERT(hWnd == m_hWnd); // should have been set in send msg 

hook
 return TRUE;
}

WINCORE.CPP is code supplied with Developer Studio. It's typically in the folder \Program Files\DevStudio\VC\mfc\src.

This sets up a CREATESTRUCT structure very much like a WNDCLASS, and fills it with the parameters that were passed to CreateEx(). It calls PreCreateWindow, AfxHookWindowCreate(), ::CreateWindow(), and AfxUnhookWindowCreate() before checking hWnd and returning.

The AFX prefix on many useful MFC functions dates back to the days when Microsoft's internal name for their class library was Application Framework. The :: in the call to CreateWindow identifies it as an API function, sometimes referred to as an SDK function in this context. The other functions are member functions of CWnd that set up other background boilerplate for you.

So, on the face of it, there doesn't seem to be any savings here. You declare an instance of some CWnd object, call its Create() function, and have to pass just as many parameters as you did in the old C way of doing things. What's the point? Well, CWnd is really a class from which to inherit. Things get much simpler in the derived classes. Take CButton, for example, a class that encapsulates the concept of a button on a dialog box. A button is just a tiny little window, but its behavior is constrained -- for example the user cannot resize a button. Its Create() member function looks like this:

BOOL CButton::Create(LPCTSTR 
lpszCaption, DWORD dwStyle,
 const RECT& rect, 
CWnd* pParentWnd, UINT nID)
{
 CWnd* pWnd = this;
 return pWnd->Create(_T("BUTTON"), lpszCaption, dwStyle, rect, 
pParentWnd, nID);
}

That's a lot less parameters. If you want a button, you create a button, and let the class hierarchy fill in the rest.

Getting a Handle on All These MFC Classes

There are over 200 MFC classes. Why so many? What do they do? How can any normal human keep track of them and know which one to use for what? Good questions. Questions that will take a pretty large piece of this book to answer. The first half of this book presents the most commonly used MFC classes. This section looks at some of the more important base classes.

CObject

Figure A.1 shows a high-level overview of the inheritance tree for the classes in MFC. Only a handful of MFC classes do not inherit from CObject. Cobject contains the basic functionality that all the MFC classes (and most of the new classes you create) will be sure to need, like persistence support and diagnostic output. As well, classes derived from CObject can be contained in the MFC container classes, discussed in Reference E, "Useful Classes."

Fig. A.1 Almost all the classes in MFC inherit from CObject.

CCmdTarget

Some of the classes that inherit from CObject, like CFile and CException, and their derived classes, do not need to interact directly with the user and the operating system through messages and commands. All the classes that do need to receive messages and commands inherit from CCmdTarget. Figure A.2 shows a bird's eye view of CCmdTarget's derived classes, generally called command targets.

Fig. A.2 Any class that will receive a command must inherit from CCmdTarget.

CWnd

As already mentioned, CWnd is a hugely important class. Only classes derived from CWnd can receive messages; threads and documents can receive commands but not messages.

Chapter 4, "Messages and Commands," explores the distinction between commands and messages. Chapter 5, "Documents and Views," explains documents, and Chapter 27, "Multitasking with Windows Threads," explains threads.

Cwnd provides window-oriented functionality like calls to CreateWindow and DestroyWindow, functions to handle painting the window in the screen, processing messages, talking to the Clipboard, and much moreóalmost 250 member functions in all. Only a handful of these will need to be overridden in derived classes. Figure A.3 shows the classes that inherit from CWnd; there are so many control classes that to list them all would clutter up the diagram, so they are lumped together as control classes.

Fig. A.3 Any class that will receive a message must inherit from CWnd, which provides lots of window-related functions.

All Those Other Classes

So you've seen ten classes so far on these three figures. What about the other 200+? You'll meet them in context, throughout the book. If thereís a specific class you were wondering about, check the index. Check the online help, too, because every class is documented there. And don't forget, the full source for MFC is included with every copy of Visual C++. Reading the source is a hard way to figure out how a class works, but sometimes you need that level of detail.


© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.