June 1999
Code for this article: June99CQA.exe (3KB)
Paul DiLascia is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992) and a freelance consultant and
writer-at-large. He can be reached at askpd@pobox.com
or http://pobox.com/~askpd.
|
Q I have been reading MSJ to unravel how MFC encapsulates the Win32® SDK, but I am not sure where AfxWndProc becomes the default window procedure. I was able to trace into AfxEndDeferRegisterClass (see Figure 1), but here WndProc is set to DefWindowProc instead of AfxWndProc. Am I missing something?
Ravi Tolani
A Yes, you're missing something. AfxEndDeferRegisterClass is not where MFC sets the window proc. So where does it? The answer is a little confusing, and there's more than one place it can happen. It's crucial to understand how and why MFC sets up its own window proc since this AfxWndProc lies at the heart of MFC. The entire message map mechanism whereby MFC routes window messages (such as WM_SETFOCUS) to your handler functions (like OnSetFocus) depends on subclassing each MFC-controlled window with the MFC universal window proc, AfxWndProc. So forgive me while I digress a bit before answering your question. You can think of AfxWndProc as a function with a big switch statement that routes WM_XXX messages to your window class's OnXXX handler functions. This is a first-order approximation of how AfxWndProc works: |
|
The true picture is, of course, much more complicated. For one thing, AfxWndProc doesn't do the dispatching. It calls another function, AfxCallWndProc, which calls CWnd::
WindowProc, which calls OnWndMsg, which does the dispatchunless the message is WM_COMMAND or WM_
NOTIFY, in which case OnWndMsg calls OnCommand or OnNotify, both of which call CCmdTarget::OnCmdMsg to do the dispatch. Furthermore, MFC does not switch on the message ID (WM_CREATE, WM_SETFOCUS, and so on), but rather on the signature of the message handler function. (I told you it was complicated!)
When OnWndMsg gets a message, it searches your window object's message map for an entry with a message ID that matches the received message. Suppose your message map has an ON_WM_SETFOCUS entry. ON_WM_ SETFOCUS is a macro that generates an entry in your message map. |
|
Among the members in the message map entry is the special code AfxSig_vW that identifies the signature of the handler function. In this case, OnSetFocus takes a CWnd pointer (W) and returns void (v)hence AfxSig_vW. OnWndMsg uses this code to dispatch to the handler function: |
|
Here, pEntry->m_pfn is your handler function (for example, OnSetFocus). Other message map entries will have different signature codes. For example, ON_WM_PAINT and ON_WM_NCPAINT generate message map entries with AfxSig_vv (the handler function has no arguments and returns void), whereas ON_WM_ENDSESSION generates an entry with AfxSig_vb: BOOL argument returning void: |
|
Why does MFC dispatch on the signature code instead of the message ID? Because one of the things MFC must do before calling your handler function is package its argumentsthat is, convert WPARAM and LPARAM into more typesafe objects such as CWnd* or BOOL. It's more efficient to lump together all handler functions with a given signature so MFC can convert the arguments before calling the handler function. There are about 50 or 55 different codes defined in afxmsg_.h (see Figure 2).
It's important to understand how MFC dispatches WM_XXX messages to your handler functionsparticularly when you find yourself immersed in debuggingbut it's equally important to realize that the message-routing code is universal for all window classes. MFC uses the same AfxWndProc for all window objects. Which leads to your original question: how does MFC subclass your window? That is, how does it get Windows® to use AfxWndProc? There are several ways it can happen, but for most purposes there are only two, depending on whether you create the window from scratch or by subclassing an existing window such as a dialog control. If you start from scratch, you create your window by calling CWnd::Create or some overloaded version, such as CView::Create or CButton::Create. Oras in the case of viewsMFC calls Create for you. In all these cases, control eventually arrives at CWnd::CreateEx. |
|
Before calling ::CreateWindowEx to create the window, MFC calls AfxHookWindowCreate; and after creating the window, it calls AfxUnhookWindowCreate. These functions set up and remove a WH_CBT (computer-based training) hook. Recall from Windows 202 (one semester beyond Windows 101) that a hook is an application callback function that Windows calls whenever certain events happen. There are several kinds of hooks; AfxHookWindowCreate uses a WH_CBT hook because, among other events, Windows calls it whenever creating or destroying a window.
Figure 3 shows how AfxHookWindowCreate sets up the WH_CBT hook. MFC installs a hook function, _AfxCbtFilterHook, then sets a thread-global variable m_pWndInit, which points to the CWnd object being created or hooked. MFC uses a global because the Windows hook mechanism doesn't provide any way of passing an argument to the hook function. So far, so good. Once the hook is set up, control passes into CWnd::CreateEx, which calls ::CreateWindowEx, a black box from MFC's point of view. Windows creates the window, la-di-dah. But immediately afterward, Windows looks at its internal structures, scratches its weary head, and says, "Oh, this app has a WH_CBT hook installed. I'd better call the callback." So Windows calls _AfxCbtFilterHook, passing information about the window being created. _AfxCbtFilterHook is really quite messynot the kind of thing to read if you can avoid it. Figure 4 shows a simplified version. I've omitted some gnarly DLL stuff to highlight the main points. _AfxCbtFilterHook attaches the HWND (sets m_hWnd) to the window object being created (pWndInit), then subclasses the window by installing the generic AfxWndProc. CWnd::GetSuperWndProcAddr returns an address where the old window proc can be saved. This virtual function has a simple default implementation: |
|
In earlier releases of MFC, you had to supply this function yourself when creating new window classes because the MFC storage police were too stingy to allocate four bytes of m_pfnSuper. Admittedly, it's a waste to have m_pfnSuper in every window object when you need only one instance for the entire classbut what the heck, the Redmondtonians decided to put m_pfnSuper in CWnd so you wouldn't have to bother with GetSuperWndProcAddr every time you define a new class. After saving the old window proc, MFC calls PreSubclassWindow, another CWnd virtual function you can override if you want to do stuff just before the window is subclassed. Why not?
Finally, MFC installs AfxWndProc as the window procthat's the answer to your question. But astute readers have already noticed that _AfxCbtFilterHook doesn't use AfxWndProc directly as the window proc; it calls a function AfxGetAfxWndProc to get the window proc. |
|
In a non-DLL app, AfxGetAfxWndProc simply returns AfxWndProc, but in a DLL the window proc is stored in the module state. That's because the DLL must use the window proc of whichever application is calling it at any given time, not its own.
Once _AfxCbtFilterHook is finished, the window is hooked up. Control flows out of _AfxCbtFilterHook, out of the black box ::CreateWindowEx, and back to CWnd::CreateEx, which now calls AfxUnhookWindowCreate to remove the CBT hook. The raison d'etre of the CBT hook is to trap the window's creation so MFC can subclass the windowthat is, install AfxWndProc. Why does MFC go through such contortions? Why not just set the window proc in WNDCLASS::lpfnWndProc when the window class is registered, the way any Windows programmer who read Petzold would do it? Good question. There are two reasons. First, the install-a-hook method works for window classes that are not yours or MFC'ssuch as the built-in classes Button, ListBox, ToolbarWindow32, and so onas well as window classes that belong to third-party libraries. OK, so why not just call SetWindowLong to subclass the window (change the window proc) after the window is created? Becauseand this is the real reason MFC must set a hookin that case your window object would never get a chance to handle WM_CREATE and WM_NCCREATE. These messages are sent by Windows itself, from within ::CreateWindowEx when Windows is creating the window. If you're still with me, relax. The hard part is over. Earlier I said that there are two ways MFC can subclass a window. The first is when you call some Create function as described previously; the second happens when you call SubclassWindow or SubclassDlgItem. Fortunately, things are much simpler in this case because the window already exists. SubclassWindow and SubclassDlgItem subclass the window directly by calling ::SetWindowLong with GWL_WNDPROC, without using any hook subterfuge. There are other ways MFC can subclass a window (for example, the common dialogs require special handling), but generally they're all just variations on the set-a-hook game that I described. If you can wrap your head around that, you'll have no trouble understanding the variations. The important things to understand are why MFC needs to subclass each window with AfxWndProc in the first place (to map WM_XXX messages to your OnXXX handler functions), and how MFC uses a CBT hook to install AfxWndProc before Windows sends any messages to the window, so your app can handle messages like WM_CREATE and WM_NCCREATE. If you understand these two points well enough to explain them to your grandmother while simultaneously juggling three beanbags and a Tinky Winky doll, you're more than halfway to becoming a certified MFC guru. Q In the Flyby program (MSJ, October and December 1998), you use the API function GetCursorPos to retrieve the position of the cursor (mouse). How likely is it that the point yielded by this function really reflects the original position of the cursor? For example, say a mouse move triggers your OnLButtonDown, which calls CMyView::DoHighlight, which eventually calls ::GetCursorPos. Let's assume that the OS suspends the process after the mouse message was dispatched, but before the call to GetCursorPos, and that the PC is slow and the user is quick. When the process is resumed, could it be possible that the mouse is not positioned at the same place any more? You can simulate this scenario by setting a breakpoint on CMouse::GetPos. After pressing F10, the mouse is no longer at the same position. In Flyby, this is harmless because all you're doing is highlighting a hot spot, but imagine what could happen when you use GetCursorPos handling a CTreeCtrl that stores a directory tree. Getting the wrong position would cause a disaster when you delete the wrong file or directory! So is there a way to get the exact mouse position that was valid when a certain Windows message was triggered? Franz Resenicek
Austria A Good point! Perhaps no user in the world can move a mouse that fast under normal conditionsbut hey, it could happen! And in the old days of Windows 3.1, it may have been more likely. This is why Windows does, in fact, provide a way to get the mouse coordinates at the time any given message was sent. Normally, a message arrives at your app when Windows calls your app's window proc. This callback function receives the HWND, message ID, WPARAM, and LPARAM. But actually, it's a little more complicated than that. Your app doesn't just get messages like a thunderbolt from the blue; it must fetch them by calling ::GetMessage. That's why 16-bit Windows (Windows 3.1) is called a cooperative, nonpreemptive multitasking operating system. In those days this held true globally; nowadays we know 32-bit versions of Windows are true (preemptive) multitasking systems. Nevertheless, within a process or thread, you still must call ::GetMessage and ::DispatchMessage to process messages in an orderly, synchronized fashion. Most every process or User Interface thread contains a central Get/Dispatch loop. This loop is called the message pump. GetMessage retrieves the next message in the queue by filling a special MSG structure. |
|
The first four members are the familiar window proc arguments; MSG.time is (what else?) the time the message was sent, and MSG.pt holds the mouse position in screen coordinates when the message was sent.
MSG.pt is just what you want. The only problem is, how do you get it? When an app (MFC or otherwise) calls ::DispatchMessage to dispatch the message, Windows calls the appropriate window proc with hwnd, message, wParam, and lParam. The time and pt get lost in the shuffle. In a C app, you could make the MSG a global, so your window proc and message handler functions could access it. But with MFC, the message pump is buried in the framework, in CWinThread::PumpMessage. So how can you get the rest of the information in the MSG? Fortunately, CWinThread retrieves the MSG into a CWinThread data member, m_curMsg. You can get the MSG from this public member, but before you go racing to the keyboard, there's an easier way: just call ::GetMessagePos. (There's also ::GetMessageTime to get the time.) GetMessagePos returns the position of the cursor when the last message was retrieved using GetMessage. Just remember: not all messages are queued. When you or someone else calls SendMessage to send a message to a window, Windows calls the window's window proc directlywithout queueing the message. Only truly external events such as user input (mouse or keyboard), hardware interrupts, and messages sent with ::PostMessage are queued. This is generally not a problem, since you are usually not interested in knowing the mouse position or time when some part of your app called SendMessage. (What meaning could it have?) Rather, you're usually interested in the mouse position or time when some real event occurred such as a mouse click or keystroke. MFC itself uses GetMessageTime and GetMessagePos to implement a function you can use to get the current MSG: CWnd::GetCurrentMessage. |
|
All messages sentand now I mean sent, not queuedto your window go through AfxCallWndProc (see the previous question). This function remembers the current message in the thread state, in a m_lastSentMsg member. |
|
GetCurrentMessage simply returns a pointer to m_lastSentMsg (const, so you can't munge it!) after updating the time and pt fields with GetMessageTime and GetMessagePos. AfxCallWndProc doesn't bother to do this because it's a waste of CPU cycles. Very rarely do you care what the message time and cursor position are, so why copy those extra bytes for every message sent? If you need these values, you can call GetCurrentMessage. But once again you have to be careful. GetCurrentMessage returns the current messagewhether queued or sent; if for some reason you want to know what the currently queued message was, you have to look in CWinThread::m_curMsg. In either case, the time and pt fields will be the same, since GetMessageTime and GetMessagePos return their respective values for the last message retrieved with GetMessagethat is, the last queued message.
If all that seems confusing, it is. The short answer is: call ::GetMessagePos or CWnd::GetMessage, whichever tickles your fancy. Ta-ta! |
Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com |
From the June 1999 issue of Microsoft Systems Journal.
|