Documentos de Académico
Documentos de Profesional
Documentos de Cultura
to a WndProc
In the 16-bit version of MFC (version 2.5), MFC registered a single message- handling
procedure called AfxWndProc() for all its windows (except for the non- MFC windows
registered by the developer). As with any other Windows application, an MFC
application’s messages are deposited in a window procedure. In the case of the 16-bit
version of an MFC application, that function was AfxWndProc(). Because the MFC
window classes were registered with AfxWndProc() as the message handler, it was
obvious where messages were handled. Following messages through the system was
easy: you just put a breakpoint in the AfxWndProc() function. Things have changed
significantly with the 32-bit version of MFC. MFC window classes are no longer
registered with the AfxWndProc() function—they’re registered with DefWindowProc()
as the message handler. When you look at the source code for MFC, you’ll find that
AfxWndProc() is still there, and it’s dispatching messages to various CWnd-derived
objects. How is it that messages end up in AfxWndProcO? Remember that MFC
maintains two of its own message hooks: _AfxMsgFilterHook() and
_AfxCbtFilterHook(). Because messages are directed to the hooks before anything else
happens, there’s an opportunity for certain messages to be intercepted in one of these
hooks. As it turns out, MFC uses the computer-based training hook function to attach
AfxWndProcO to the MFC windows as they’re created. Here’s how AfxWndProcO is
hooked up to MFC’s windows.
MFC installs the _AfxCbtFilterHook() function whenever a new CWnd-derived object is
created—that is, during a call to CWnd: :GeateEx(). Right before CWnd: :CreateEx()
makes a call to the CreateWindowEx() API function, CWnd::CreateEx() calls
AfxHookWindowCreate(). AfxHookWindowCreate() inserts _AfxCbtFilterHook() into
the hook chain. Because _AfxCbtFilterHook() is a computer-based training hook,
Windows calls _AfxCbtFilterHook() before activating, creating, destroying, minimizing,
maximizing, moving, or sizing a window. Windows also calls _AfxCbtFilterHook before
completing a system command, before removing a mouse or keyboard event from the
system message queue, before setting the keyboard focus, and before synchronizing with
the system message queue.
_AfxCbtFilterHook() sits there receiving the messages as just described.
_AfxCbtFilterHook() ignores window messages until the HCBT_CREATEWND code is
passed into _AfxCbtFilterHook(). When _AfxCbtFilterHook() is called with the
HCBT_CREATEWND code, that means a window is about to be created.
_AficCbtFilterHookO then calls _AfxStandardSubclass().
_AfxStandardSubclass() uses SetWindowLongO to wire AfxWndProc() up to the
window. From now on, messages for that window will go to AfxWndProc(), where they
are handled by the command-routing architecture. So even though the window was
originally registered with DefWindowProc() as the message-handling procedure, the
framework effectively wires the windows up to AfxWndProc() whenever a CWnd-
derived window is created.
The main reason Microsoft shifted from using AfxWndProc() as the registered window
procedure to using DefWindowProc() is to support 3D Controls, which works through
Microsoft’s CTL3D.DLL. It is desirable to have CTL3D functionality appear to be part
of the system. This is so an app that wants to override CTL3D functionality (in handling
WM_CTLCOLOR messages, primarily) can do so. In order for this to work, MFC has to
ensure that subclassing is in the following order (from first called to last called):
AfxWndProc(), CTL3D’s WndProc(), and finally DefWindowProc(). In order to do this,
Microsoft had to allow CTL3D to subclass before AfxWndProc(), which meant delaying
hooking up AfxWndProc() until after CTL3DSubclassDlgEx is called. So MFC registers
everything with DefWindowProc(), then during _AfxStandardSubclass(), hooks up
CTL3D, then finally adds a subclass on top of CTL3D by installing AfxWndProcO as the
final “top” window procedure. For backward compatibility Microsoft still supports the
idea of registering with AfxWndProcO from the start (you’ll see extra tests in
_AfxStandardSubclass() to handle this).
Handling Messages
Remember that MFC represents windows in two ways: (1) by a unique system- defined
window handle and (2) by the C++ class representing the window. Window handles are
wrapped by CWnd and CWnd-derived classes. It’s easy to get the window handle from a
CWnd object because the window handle is a data member of the class. However, there is
no way to get from the window handle to the CWnd object without some extra footwork.
As you saw earlier, MFC uses a CMapPtrToPtr object to map handles to CWnd objects.
MFC maintains the link for the lifetime of the window. When a window is created using
CWnd (or a CWnd-derived class), the window handle is attached to the CWnd object.
That is, the window handle and the CWnd object are associated through the handle map.
MFC does this so that the framework code can deal with C++ objects rather than window
handles.
And now back to the window procedure. AfxWndProcO handles a single specific
message: WM_QUERYAFXWNDPROC. If the incoming message is
WM_QUERYAFXWNDPROC, AfxWndProc() returns the number 1. Applications can
send the WM_QUERYAFXWNDPROC message to find out if the window is an MFC
window using MFC’s message-mapping system. The return value for the
WM_QUERYAFXWNDPROC message is also used by CWnd’s Dump procedure for
diagnostic information. If the message isn’t WM_QUERYAFXWNDPROC,
AfxWndProc() goes on to process the message.
Inside AfxWndProc(), the framework retrieves the C++ object associated with the
window handle using CWnd::FromHandlePermanent(). The framework then calls
AfxCallWndProc(). Notice that even though the first parameter is a pointer to a CWnd
object, the second parameter is a window handle. This allows AfxCallWndProc() to
maintain a record of the last message processed for use in handling exceptions and
debugging. Here’s the prototype for AfxCallWndProc(). Notice how it looks like any
other window procedure, except that the parameter includes a CWnd pointer as well.
LRESULT PASCAL _AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
AfxCallWndProc() first examines the message to see if it’s a WM_INITDIALOG
message, in which case it calls _AfxHandleInitDialog(). _AfxHandleInitDialog() is for
the auto-center dialog feature. MFC caches certain styles before the dialog handles
WM_INITDIALOG. If it is appropriate to center the window (the window is still not
visible and hasn’t moved), then MFC automatically centers the dialog against its parent.
The function AfxCallWndProc() saves the window handle, the message, and the
WPARAM and the LPARAM in the current thread state’s m_lastSentMsg member
variable. Then AfxCallWndProc() simply calls the window object’s window procedure.
Here’s the prototype for WindowProc():
LRESULT CWnd::WindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam);
Notice that the signature has the same parameters as a regular window procedure. That’s
because at this point, MFC has already mapped the window handle to the existing CWnd-
derived class.
By the way, CWnd::WindowProc() is virtual, so you can override it if you’d like. One
reason you may want to override CWnd::WindowProc() is if you want to handle a
message before MFC even looks at it. For example, you may have a class that bypasses
the message-mapping system—perhaps to improve performance. CWnd::WindowProc()
calls CWnd::OnWndMsg(). If OnWndMsgO returns FALSE, then CWnd::WindowProc()
calls CWnd::DefWindowProc(). The action really begins inside CWnd::OnWndMsg().
Listing 3-3 shows some pared-down source code.
ASSERT(FALSE);
break;
// Examine the signature type, calling the
// message handler with the appropriate parameters
}
goto LReturnTrue;
LDispatchRegistered: // for registered windows messages ASSERT(message >=
OxCOOO); mmf.pfn = lpEntry->pfn; lResult = (this->*mmf.pfn_lwl)(wParam, lParam);
LReturnTrue:
if (pResult != NULL)
*pResult = lResult;
return TRUE;
}
This is a fairly hefty function. Remember, we’re replacing that unruly switch statement.
Let’s briefly walk through OnWndMsgO before tracing messages through it. First,
OnWndMsgO tries to filter out certain messages from the get-go: WM_COMMAND,
WM_NOTIFY, WM_ACTIVATE, and WM_SETCURSOR. The framework has special
ways of handling each of these messages. If the message isn’t one of those just listed,
OnWndMsgO tries to look up the message in the message map. MFC keeps a message
map entry cache that is accessible via a hash value. This is a great optimization because
looking up a value in a hash table is much cheaper than walking the message map.
This is where commands and regular window messages go their separate ways. If the
message is a command message (that is, nMsg == WM_COMMAND), then
CWnd:.OnWndMsgO calls OnCommand(). Otherwise, it retrieves the window object’s
message map to process the message (more on that shortly). Let’s examine the command
routing first.
Handling WM_COMMAND
The first stop a command makes on its way to its designated command target is
CWnd::OnCommand().
CWnd::OnCommandO
OnCommand() is a virtual function, so the framework calls the correct version. In this
example, because the message was generated for the main frame window, the framework
calls the CFrameWnd version of OnCommand(). BOOL
CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam);
By this point, the message is pared down to just its WPARAM and its LPARAM. If the
message is a request for on-line help, the framework sends a WM_COMMANDHELP
message to the frame window. Otherwise, the message is passed on to the base class’s
OnCommandO, CWnd::OnCommand().
OnCommand() examines the LPARAM, which represents the control. If the command
was generated by a control, then the LPARAM contains the control window. If the
message is a control notification (like LBN_CHANGESEL), then the framework
performs some special processing. If the message is for a control, OnCommandO sends
the last message straight to the control, then OnCommandO returns. Otherwise,
CWnd::OnCommand() makes sure that the user-interface element that generated the
command has not become disabled (for instance, a menu item) and passes the message on
to OnCmdMsgO (which is also virtual). Again, because the frame window is still trying
to handle the message, CFrameWnd: :OnCmdMsg() is the version that’s called. This
function is found in WINFRM.CPP:
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerlnfo);
CFrameWnd::OnCmdMsg() passes NULL for pExtra and pHandlerlnfo when it calls
CFrameWnd::OnCmdMsg(), because this information is not needed for handling
commands.
CFrameWnd::OnCmdMsg() pumps the message through the application components in
this order:
• The active view
• The active view’s document
• The main frame window
• The application
To route the command to the active view, CFrameWnd::OnCmdMsg() tries to find the
frame’s active view using CWnd: :GetActiveView(). If CFrameWnd::OnCmdMsg()
succeeds in finding the frame’s active window, it calls the active view’s OnCmdMsg().
If the active view’s OnCmdMsgO can’t deal with the command, the document gets a
crack at the command. If CFrameWnd::OnCmdMsg() fails to find an active view, or the
view and the document fail to handle the message, then the frame window gets a chance
to handle the message. Finally, if the frame window doesn’t want the message, then the
application takes a crack at the message—CFrameWnd::OnCmdMsg() calls the
application’s OnCmdMsgO function.
At this point in the example, the message has reached the active view and the function
CView::OnCmdMsg(), found in VIEWCORE.CPP:
BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerlnfo);
The framework gives the window pane part of the view a chance to respond to the
message by calling CWnd::OnCmdMsg(). If the view pane can’t handle the message, it’s
pumped through the document.
Because CWnd doesn’t override OnCmdMsgO, the command goes straight to
CCmdTarget::OnCmdMsg(), found in CMDTARG.CPP:
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerlnfo);
CCmdTarget::OnCmdMsgO walks the message map (back to the base class if it has to)
trying to find a handler for the message. If it finds one, it calls that function. If it can’t,
CCmdTarget::OnCmdMsg() returns FALSE, and the document gets a chance to handle
the message. If the document doesn’t want anything to do with the message, then the
message is handled by the CWnd’s DefWindowProc(). If CCmdTarget::OnCmdMsg()
finds a handler in the message map, then it calls DispatchCmdMsgO, also in
CMDTARG.CPP:
static BOOL DispatchCmdMsg(CCmdTarget* pTarget, UINT nID, mt nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO*
pHandlerlnfo);
This function is declared static, so it’s visible only within CMDTARG.CPP One of the
parameters is the function signature. This signature comes from the message map entry
itself. (Remember, one of the fields of the message map structure is a function signature.)
Also notice that a pointer to the message handler function (pfn) is passed as a parameter.
This function pointer is also found within the message map entry.
DispatchCmdMsgO switches on the function signature, performing different operations
depending on whether the signature is for a regular command, an extended command, a
command user-interface handler, or a Visual Basic control. In the case of a regular menu
command, the signature is AfxSig_vv (void return, void parameter list).
DispatchCmdMsgO immediately calls the message handler, and— voila—the handler for
that message is called.
If CCmdTarget::OnCmdMsg() fails to find a handler within the message map, it returns
FALSE, which eventually causes CWnd::DefWindowProc() to handle the message.
MFC uses this command-routing scheme for all CCmdTarget-derived classes.
That includes classes derived from CWnd, CDocument, CView, and CFrameWnd. One
interesting aspect of this arrangement is the path that commands take to get to their final
destinations. All command messages take the same path for the first three steps. That is,
the message first lands in AfxWndProc(), which gets the CWnd object from the HWND
parameter and calls AfxCallWndProc(). AfxCallWndProc() calls the CWnd-derived
object’s WindowProc(). From there, the message is routed to its intended destination.
Here’s a rundown of the path a command message takes to the various components of an
MFC application.
Command to a Document
Here is the path a WM_COMMAND message takes to an application’s document:
AfxWndProc()
AfxCallWndProc()
CWnd::WindowProc()
CWnd::OnWndMsg()
CFrameWnd::OnCommand()
CWnd::OnCommand()
CFrameWnd::OnCmdMsg()
CView::OnCmdMsg()
CDocument::OnCmdMsg()
CCmdTarget::OnCmdMsg()
DispatchCmdMsg()
CSdiappDoc::OnDoccommandAdoccommand()
Command to a View
Here is the path a WM_COMMAND message takes to an application’s view:
AfxWndProc()
AfxCallWndProc()
CWnd::WindowProc()
CWnd::OnWndMsg()
CFrameWnd::OnCommand()
CWnd::OnCommand()
CFrameWnd::OnCmdMsg()
CView::OnCmdMsg()
CCmdTarget::OnCmdMsg()
DispatchCmdMsg()
CSdiappView::OnViewAviewcommand()
Command to an App
Here is the path a WM_COMMAND message takes to an application’s CWinApp-
derived object:
AfxWndProc()
AfxCallWndProc()
CWnd::WindowProc()
CWnd::OnWndMsg()
CFrameWnd::OnCommand()
CWnd::OnCommand()
CFrameWnd::OnCmdMsg()
CCmdTarget::OnCmdMsg()
DispatchCmdMsg()
CSdiappApp::OnAppAnappcommand()
Command to a Dialog Box
Dialog boxes also receive command messages. Here is the path a WM_COMMAND
message takes to a dialog box:
AfxWndProc()
AfxCallWndProc()
CWnd::WindowProc()
CWnd::OnWndMsg()
CWnd::OnCommand()
CDialog::OnCmdMsg()
CCmdTarget::OnCmdMsg()
DispatchCmdMsg()
CAboutDlg::OnAButton()
So that’s how command messages come through the framework. As you can see, the
message goes caroming between several different classes. Handling regular window
messages (like WM_SIZE) is quite a bit simpler.
AfxFindMessageEntryO
AfxFindMessageEntryO is an interesting function. There are actually two versions of it:
one written in assembly language and one written in C. If the application is compiled for
an Intel-based machine, then AfxFindMessageEntryO uses the assembly version.
Otherwise, AfxFindMessageEntryO uses the C version. To find the message map entry
in the table, OnWndMsgO first retrieves the CWnd object’s message map. Then, given
the first entry in that message map, AfxFindMessageEntryO walks the array of message
map entries until it either (a) finds the message in the message map or (b) finds the end of
the message map as marked by the AfxSig_end signature (remember, that’s what the
END_MESSAGE_MAP macro does).
When OnWndMsgO finds a handler, it calls the handler. If the message map doesn’t
include a handler for a specific message, the framework calls the window’s default
window procedure. So, unlike commands, which are routed to several places, regular
window messages go straight to the window for which they were intended. For example,
take a look at the path a WM_SIZE message takes to its destination (a CWnd-derived
class).
WM_ACTIVATE
MFC’s messaging architecture also handles WM_ACTIVATE. OnWndMsgO calls the
non-C++ member function _AfxHandleActivate(). This function checks to see if the
WM_AOTVATE message is for the top-level window. If it is, _AfxHandleActivate()
sends the WM_ACTIVATETOPLEVEL message to the top-level window.
WM_SETCURSOR
MFC handles the WM_SETCURSOR message within the function
_AixHandleSetCursorQ. _AfxHandleSetCursor() checks to see if one of the mouse
buttons is pressed. If a mouse button is down, _AfxHandleSetCursor() activates the last
active window. This is necessary to work around a Windows bug. If a window is disabled
and a modal dialog is parented to that disabled window, the app will not activate itself
correctly when the user clicks on the disabled window. Instead, the user must click
directly on the dialog itself. If the dialog is small, it is probably covered. Normally,
clicking on the disabled window would result in just a beep from DefWindowProc() in
this circumstance. MFC fixes this Windows bug by activating the dialog box (and thus
bringing the app to the top) when you click on a disabled window with an active modal
dialog box (or other modal window).
Hooking into the Message Loop:
PreTranslateMessageQ
MFC has another useful feature: you can plug into the message loop to handle messages
before they ever get to their designated command targets or windows. The function for
doing that is PreTranslateMessage(). MFC gives you two places where you can hook into
the message loop: CWinApp::PreTranslateMessageO and CWnd::PreTranslateMessageO.
CWinApp::PreTranslateMessage() lets you process window messages even before they
get to any of your application’s windows or command targets. CWinApp::Run() calls
CWinApp::PreTranslateMessage() before the message is processed by
TranslateMessageO and DispatchMessage(). CWndApp: :PreTranslateMessage() takes a
single parameter: a pointer to an MSG structure. By default, the framework examines the
message. If the message is a mouse button down or a mouse button double-click,
CWndApp ::PreTranslateMessage() removes any tool tips from the screen by calling
CancelToolTip().
Then CWinApp::PreTranslateMessage() calls each of the windows from the target
window (as designated by the window handle in the message structure) to the
application’s main window. CWnd::WalkPreTranslateTree() performs this function, as
shown in Listing 3-5.