How Message Maps Work

来源:百度文库 编辑:神马文学网 时间:2024/04/27 02:53:17
You can find out how message maps work by examining the DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, and END_MESSAGE_MAP macros in Afxwin.h and the code for CWnd::WindowProc in Wincore.cpp. Here‘s a synopsis of what goes on under the hood when you use message-mapping macros in your code, and how the framework uses the code and data generated by the macros to convert messages into calls to corresponding class member functions.
MFC‘s DECLARE_MESSAGE_MAP macro adds three members to the class declaration: a private array of AFX_MSGMAP_ENTRY structures named _messageEntries that contains information correlating messages and message handlers; a static AFX_MSGMAP structure named messageMap that contains a pointer to the class‘s _messageEntries array and a pointer to the base class‘s messageMap structure; and a virtual function named GetMessageMap that returns messageMap‘s address. (The macro implementation is slightly different for an MFC application that‘s dynamically rather than statically linked to MFC, but the principle is the same.) BEGIN_MESSAGE_MAP contains the implementation for the GetMessageMap function and code to initialize the messageMap structure. The macros that appear between BEGIN_MESSAGE_MAP and END_MESSAGE_MAP fill in the _messageEntries array, and END_MESSAGE_MAP marks the end of the array with a NULL entry. For the statements
// In the class declarationDECLARE_MESSAGE_MAP ()// In the class implementationBEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd) ON_WM_PAINT ()END_MESSAGE_MAP ()
the compiler‘s preprocessor generates this:
// In the class declarationprivate: static const AFX_MSGMAP_ENTRY _messageEntries[];protected: static const AFX_MSGMAP messageMap; virtual const AFX_MSGMAP* GetMessageMap() const;// In the class implementationconst AFX_MSGMAP* CMainWindow::GetMessageMap() const { return &CMainWindow::messageMap; }const AFX_MSGMAP CMainWindow::messageMap = { &CFrameWnd::messageMap, &CMainWindow::_messageEntries[0]};const AFX_MSGMAP_ENTRY CMainWindow::_messageEntries[] = { { WM_PAINT, 0, 0, 0, AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(void (CWnd::*)(void))OnPaint }, {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }};
With this infrastructure in place, the framework can call GetMessageMap to get a pointer to CMainWindow‘s messageMap structure. It can then scan the _messageEntries array to see if CMainWindow has a handler for the message, and if necessary it can grab a pointer to CFrameWnd‘s messageMap structure and scan the base class‘s message map, too.
That‘s a pretty good description of what happens when a message for CMainWindow arrives. To dispatch the message, the framework calls the virtual WindowProc function that CMainWindow inherits from CWnd. WindowProc calls OnWndMsg, which in turn calls GetMessageMap to get a pointer to CMainWindow::messageMap and searches CMainWindow::_messageEntries for an entry whose message ID matches the ID of the message that is currently awaiting processing. If the entry is found, the corresponding CMainWindow function (whose address is stored in the _messageEntries array along with the message ID) is called. Otherwise, OnWndMsg consults CMainWindow::messageMap for a pointer to CFrameWnd::messageMap and repeats the process for the base class. If the base class doesn‘t have a handler for the message, the framework ascends another level and consults the base class‘s base class, systematically working its way up the inheritance chain until it finds a message handler or passes the message to Windows for default processing. Figure 1-5 illustrates CMainWindow‘s message map schematically and shows the route that the framework travels as it searches for a handler to match a given message ID, beginning with the message map entries for CMainWindow.
What MFC‘s message-mapping mechanism amounts to is a very efficient way of connecting messages to message handlers without using virtual functions. Virtual functions are not space-efficient because they require vtables, and vtables consume memory even if the functions in them are not overridden. The amount of memory used by a message map, in contrast, is proportional to the number of entries it contains. Since it‘s extremely rare for a programmer to implement a window class that includes handlers for all of the different message types, message mapping conserves a few hundred bytes of memory just about every time a CWnd is wrapped around an HWND.

Figure 1-5. Message-map processing.
Microsoft Windows 98 and Microsoft Windows NT use two different character sets to form characters and strings. Windows 98 and its predecessors use the 8-bit ANSI character set, which is similar to the ASCII character set familiar to many programmers. Windows NT and Windows 2000 use the 16-bit Unicode character set, which is a superset of the ANSI character set. Unicode is ideal for applications sold in international markets because it contains a rich assortment of characters from non-U.S. alphabets. Programs compiled with ANSI characters will run on Windows NT and Windows 2000, but Unicode programs run slightly faster because Windows NT and Windows 2000 don‘t have to perform an ANSI-to-Unicode conversion on every character. Unicode applications won‘t run on Windows 98, period, unless you convert every character string passed to Windows from Unicode to ANSI format.
When an application is compiled, it is compiled to use either ANSI or Unicode characters. If your application will be deployed on both Windows 98 and Windows 2000, it may behoove you to make strings character set neutral. Then, by making a simple change to the project‘s build settings or adding a #define to a header file, you can tell the compiler whether to produce an ANSI build or a Unicode build. If you encode a string literal like this:
"Hello"
the compiler forms the string from ANSI characters. If you declare the string like this:
L"Hello"
the compiler uses Unicode characters. But if you use MFC‘s _T macro, like this:
_T ("Hello")
the compiler will emit Unicode characters if the preprocessor symbol _UNICODE is defined, and ANSI characters if it is not. If all your string literals are declared with _T macros, you can produce a special Windows NT_only build of your application by defining _UNICODE. Defining this symbol implicitly defines a related symbol named UNICODE (no underscore), which selects the Unicode versions of the numerous Windows API functions that come in both ANSI and Unicode versions. Of course, if you‘d like the same executable to run on either platform and you‘re not concerned about the performance hit an ANSI application incurs under Windows NT, you can forget about the _T macro. I‘ll use _T throughout this book to make the sample code character set neutral.
Is wrapping string literals in _T macros sufficient to make an application completely agnostic with regard to character sets? Not quite. You must also do the following:
Declare characters to be of type TCHAR rather than char. If the _UNICODE symbol is defined, TCHAR evaluates to wchar_t, which is a 16-bit Unicode character. If _UNICODE is not defined, TCHAR becomes plain old char.
Don‘t use char* or wchar_t* to declare pointers to TCHAR strings. Instead, use TCHAR* or, better yet, the LPTSTR (pointer to TCHAR string) and LPCTSTR (pointer to const TCHAR string) data types.
Never assume that a character is only 8 bits wide. To convert a buffer length expressed in bytes to a buffer size in characters, divide the buffer length by sizeof(TCHAR).
Replace calls to string functions in the C run-time library (for example, strcpy) with the corresponding macros in the Windows header file Tchar.h (for example, _tcscpy).
Consider the following code snippet, which uses the ANSI character set:
char szMsg[256];pWnd->GetWindowText (szMsg, sizeof (szMsg));strcat (szMsg, " is the window title");MessageBox (szMsg);
Here‘s what the same code would look like if it were revised to be character set neutral:
TCHAR szMsg[256];pWnd->GetWindowText (szMsg, sizeof (szMsg) / sizeof (TCHAR));_tcscat (szMsg, _T (" is the window title"));MessageBox (szMsg);
The revised code uses the generic TCHAR data type, it makes no assumptions about the size of a character, and it uses the TCHAR-compatible string-concatenation function _tcscat in lieu of the more common but ANSI character set_dependent strcat.
There‘s more that could be said about ANSI/Unicode compatibility, but these are the essentials. For additional information, refer to the online documentation that comes with Visual C++ or to Jeffrey Richter‘s Advanced Windows (1997, Microsoft Press), which contains an excellent chapter on Unicode and a handy table listing the string macros defined in Tchar.h and their C run-time counterparts.
The CD in the back of this book contains everything you need to use the Hello program in Visual C++. The folder named \Chap01\Hello contains the program‘s source code as well as the files that make up a Visual C++ project. To open the project, simply select Open Workspace from Visual C++‘s File menu and open Hello.dsw. If you modify the application and want to rebuild it, select Build Hello.exe from the Build menu.
You don‘t have to use the Hello files on the CD. If you‘d prefer, you can create your own project and type in the source code. Here are step-by-step instructions for creating a new project in Visual C++ 6:
Select New from the Visual C++ File menu, and click the Projects tab to go to the Projects page.
Select Win32 Application, and enter a project name in the Project Name text box. If desired, you can change the path name—the drive and folder where the project and its source code will be stored—in the Location text box. Then click OK.
In the Win32 Application window, select An Empty Project and then click Finish.
Add source code files to the project. To enter a source code file from scratch, select New from the File menu, select the file type, and enter the file name. Be sure the Add To Project box is checked so that the file will be added to the project. Then click OK, and edit the file as you see fit. To add an existing source code file to a project, go to the Project menu, select Add To Project and then Files, and pick the file.
Select Settings from the Project menu. In the Project Settings dialog box, be sure that the project name is selected in the left pane and then click the General tab if the General page isn‘t already displayed. Select Use MFC In A Shared DLL from the drop-down list labeled Microsoft Foundation Classes, and then click OK to register the change with Visual C++.
Choosing Use MFC In A Shared DLL minimizes your application‘s executable file size by allowing MFC to be accessed from a DLL. If you choose Use MFC In A Static Library instead, Visual C++ links MFC code into your application‘s EXE file and the file size grows considerably. Static linking uses disk space less efficiently than dynamic linking because a hard disk containing 10 statically linked MFC applications contains 10 copies of the same MFC library code. On the other hand, an application that is statically linked can be run on any PC, whether or not the MFC DLL is present. It‘s your call whether to link to MFC statically or dynamically, but remember that if you distribute a dynamically linked EXE, you‘ll need to distribute the DLL that houses MFC, too. For a release-build MFC application created with Visual C++ version 6, that DLL is named Mfc42.dll if the program uses ANSI characters and Mfc42u.dll if it uses Unicode characters.
Before we move on, let‘s pause for a moment and review some of the important concepts learned from the Hello application. The very first thing that happens when the application is started is that a globally scoped application object is created. MFC‘s AfxWinMain function calls the application object‘s InitInstance function. InitInstance constructs a window object, and the window object‘s constructor creates the window that appears on the screen. After the window is created, InitInstance calls the window‘s ShowWindow function to make it visible and UpdateWindow to send it its first WM_PAINT message. Then InitInstance returns, and AfxWinMain calls the application object‘s Run function to start the message loop. WM_PAINT messages are converted by MFC‘s message-mapping mechanism into calls to CMainWindow::OnPaint, and OnPaint draws the text "Hello, MFC" in the window‘s client area by creating a CPaintDC object and calling its DrawText function.
If you‘re coming to MFC straight from the Windows SDK, this probably seems like a pretty strange way to do business. Two-step window creation? Application objects? No more WinMain? It‘s definitely different from the way Windows used to be programmed. But compare Hello‘s source code to the C program listing back in Figure 1-2, and you‘ll find that MFC undeniably simplifies things. MFC doesn‘t necessarily make the source code easier to understand—after all, Windows programming is still Windows programming—but by moving a lot of the boilerplate stuff out of the source code and into the class library, MFC reduces the amount of code you have to write. That, combined with the fact that you can modify the behavior of any MFC class by deriving from it a class of your own, makes MFC an effective tool for programming Windows. The benefits will really become apparent when you begin tapping into some of the more sophisticated features of Windows or building ActiveX controls and other Windows-based software components. With MFC, you can get an ActiveX control up and running in nothing flat. Without it—well, good luck.
Hello lacks many of the elements that characterize a full-blown Windows program, but it‘s still a good first step on the road to becoming an MFC programmer. In subsequent chapters, you‘ll learn about menus, dialog boxes, and other components of an application‘s user interface. You‘ll also see how Windows programs read input from the mouse and keyboard and learn more about drawing in a window.Chapter 2 leads off by introducing some additional CDC drawing functions and demonstrating how to add scroll bars to a frame window so that you can view a workspace larger than the window‘s client area. Both are essential next steps in building the knowledge base required to become a Windows programmer.