WIN31.DOC file accompanying version 3.1 of Borland
Technical Notes Database TN2542C.txt WIN31.DOC file accompanying version 3.1 of Borland Category :OTHER Platform :All Product :BCW 3.x Description: CONTENTS ___________________________________________________________________________ Changes to windows.h from 3.0 to Message cracker examples . . 26 3.1 . . . . . . . . . . . . . . . 1 Message handler function Catching coding errors at signatures . . . . . . . . . 28 compile-time: STRICT . . . . . . . 1 Improving reusability: Why STRICT? . . . . . . . . . . 2 Template_DefProc . . . . . . 28 Compiling 3.0 applications . . . 3 Private and registered window New typedefs, constants, and messages . . . . . . . . . . 29 helper macros . . . . . . . . . 4 Message crackers and window COMSTAT structure change . . . 5 instance data . . . . . . . . 30 Making your code STRICT Message crackers and dialog compliant . . . . . . . . . . . 6 procedures . . . . . . . . . 33 STRICT conversion hints . . . 10 Message crackers and window Common compiler warnings and subclassing . . . . . . . . . 34 errors . . . . . . . . . . . . 11 Dialog procedures: A better Macro APIs and message crackers . 14 way . . . . . . . . . . . . . 36 Macro APIs . . . . . . . . . . 15 Executing default dialog 3.1-only macro APIs . . . 18 procedure functionality . . 37 Control message APIs . . . . . 18 Returning message results from Control API Examples . . . . 18 dialog procedures . . . . . 37 Message cracker macros . . . . 22 How it works . . . . . . . 37 Using message crackers and A simplified example using forwarders . . . . . . . . . 23 predefined macro APIs . . . 39 Saving time and improving Converting existing code to use readability with HANDLE_MSG . 24 message crackers . . . . . . 41 How message crackers work . 25 i TABLES ___________________________________________________________________________ 1: New handle types . . . . . . . 5 ii =========================================================================== Changes to windows.h from 3.0 to 3.1 =========================================================================== The windows.h header file included with Borland C++ 3.1 contains various features that make application development faster and easier by helping you find problems as you compile your code. These improvements include: o STRICT option provides stricter type checking, helping you find type mismatch errors quickly. o windows.h has been completely reorganized so that related functions, types, structures, and constants are grouped together. o New UINT type used for 32-bit Windows upward compatibility o New unique typedefs for all handle types, such as HINSTANCE and HMODULE. o Various constants and typedefs missing in the 3.0 windows.h have been added. o Windows 3.0 compatibility: windows.h can be used to compile applications that run under Windows 3.0. o Proper use of "const" for API pointer parameters and structure fields where pointer is read-only. If you have ObjectWindows, also see OWL31.DOC in your OWL\DOC directory for details about how these changes affect ObjectWindows and your ObjectWindows applications. =========================================================================== Catching coding errors at compile-time: STRICT =========================================================================== The new windows.h supports an option called STRICT that enables the strictest possible compiler error checking. Strict compile-time checking helps you find programming errors when you compile your application, rather than at runtime. The idea is that you define STRICT before including windows.h, which causes the various types and function prototypes in windows.h to be declared in a way that enforces very strict type checking. For example, without STRICT, it is possible to pass an HWND to a function that requires an HDC without any kind of compiler warning: with STRICT defined, this results in a compiler error. Specific features provided by the STRICT option include: o Strict handle type checking (you can't pass an HWND where an HDC is declared). o Correct and more consistent declaration of certain parameter and return value types (for example, GlobalLock returns void FAR* instead of LPSTR). o Fully prototyped typedefs for all callback function types (for example, dialog procedures, hook procedures, and window procedures) o Windows 3.0 backward compatible: STRICT can be used with the 3.1 windows.h for creating applications that will run under Windows 3.0. o The COMM DCB and COMSTAT structures are now declared in an ANSI compatible way. Why STRICT? ======================================================= The best way to think of STRICT is as a way for you to get the most out of the error checking capabilities built into Borland C++. STRICT is of great benefit especially with code under development, because it helps you catch bugs right away when you compile your code, rather than having to track it down at runtime with a debugger. By catching certain kinds of bugs right away, it's less likely that you'll ship your applications with bugs that weren't encountered in testing. STRICT also makes it easier to migrate your code to the 32-bit Windows platform later, because it will help you locate and deal with type incompatibilities that will arise when migrating to 32 bits. - 2 - It's not very difficult to convert your application to use STRICT, and it can be done in stages if needed. In order to take advantage of the STRICT option, you will probably have to make some simple changes to your source code (described in detail later). We think you'll find that STRICT makes modifying, maintaining, and even reading your code much easier, and well worth the effort to convert your application. Compiling 3.0 ======================================================= applications Unless you define STRICT, your 3.0 applications will compile with windows.h without serious modifications. The type declarations for many of the Windows APIs and callback functions have changed; those changes are backward compatible for C code, but not for C++ code. If you use C++, you'll notice compile- and link-time errors in many Windows API functions because of changes to types like WORD to UINT. See the following sections for more information. All of the features of Borland C++ 3.1 can be used to develop applications that will run under Windows 3.0. There are two things you must do: 1. Define WINVER to 0x0300 before including windows.h This ensures that only 3.0 compatible functions, structures, and definitions are available for use. You can do this in your makefile with -DWINVER=0x0300 in the compiler command line, in the IDE in the Options|Compiler|Code Generation|Defines input box, or in your code by adding "#define WINVER 0x0300" before you include windows.h. 2. Use the -30 parameter to BRC or RC. This marks your executable as a 3.0 application, so that Windows 3.0 won't prevent it from running with a "This application requires a later version of Windows" message. You will typically run RC twice in your makefile: Once to produce the .res file (with the -r switch), and a second time to combine your linked .exe and .res files into the final application. The -30 parameter must be used with the - 3 - second invocation of RC. See Chapter 4 in the Borland C++ User's Guide for instructions on how to add the -30 parameter to your IDE projects. New typedefs, ======================================================= constants, and helper macros The following typedefs and constants have been added to windows.h. All are 3.0 compatible: WINAPI Used in place of FAR PASCAL in API declarations. If you are writing a DLL with exported API entry points, you can use this for your own APIs. CALLBACK Used in place of FAR PASCAL in application callback routines such as window procedures and dialog procedures LPCSTR Same as LPSTR, except used for read-only string pointers. Typedefed as const char FAR*. UINT Portable unsigned integer type whose size is determined by host environment (16 bits for Win 3.1). Synonym for "unsigned int". Used in place of WORD except in the rare cases where a 16-bit unsigned quantity is desired even on 32-bit platforms. LRESULT Type used for declaration of all 32-bit polymorphic return values. LPARAM Type used for declaration of all 32-bit polymorphic parameters. WPARAM Type used for declaration of all 16-bit polymorphic parameters. MAKELPARAM(low, Macro used for combining two 16-bit quantities into an high) LPARAM. MAKELRESULT(low, Macro used for combining two 16-bit quantities into an high) LRESULT. MAKELP(sel, off) Macro used for combining a selector and an offset into a FAR VOID* pointer. SELECTOROF(lp) Macro used to extract the selector part of a far ptr. Returns a UINT. - 4 - OFFSETOF(lp) Macro used to extract the offset part of a far ptr. Returns a UINT. FIELDOFFSET(type, Macro used for calculating the offset of a field in a field) data structure. The type parameter is the type of structure, and field is the name of the field whose offset is desired. ------------------------------------------------------- New handle types Typedef Meaning ------------------------------------------------------- HINSTANCE Instance handle type HMODULE Module handle type HLOCAL Local handle type HGLOBAL Global handle type HTASK Task handle type HFILE File handle type HRSRC Resource handle type HGDIOBJ Generic GDI object handle type (except HMETAFILE) HMETAFILE Metafile handle type HDWP DeferWindowPos handle HACCEL Accelerator table handle 3.1 only HDRVR Driver handle ------------------------------------------------------- ------------------ The 3.0 declaration of the COMSTAT structure was not COMSTAT structure ANSI compatible: ANSI does not allow the use of change BYTE-sized bitfield declarations. To allow windows.h to ------------------ be used with full ANSI compliance, the COMSTAT structure has changed. The 7 bit fields below are now accessed as byte flags of the single status field: ------------------------------------------------------- Old field name Bit of status field ------------------------------------------------------- fCtsHold CSTF_CTSHOLD fDsrHold CSTF_DSRHOLD fRlsdHold CSTF_RLSDHOLD fXoffHold CSTF_XOFFHOLD fXoffSent CSTF_XOFFSENT - 5 - fEof CSTF_EOF fTxim CSTF_TXIM ------------------------------------------------------- No change is required if you are compiling with WINVER set to 0x0300 and are not using STRICT. If you have code that accesses any of these fields, here's how you have to change your code: Old code New code --------------------------------------------------------------------------- if (comstat.fEof || ...) if ((comstat.status & CSTF_EOF) || ...) comstat.fCtsHold = TRUE; comstat.status |= CSTF_CTSHOLD; comstat.fTxim = FALSE; comstat.status ~= CSTF_TXIM; Be careful to properly parenthesize "&" expressions. See windows.h for more details. Making your code ======================================================= STRICT compliant Using STRICT with your existing Windows application code is not very difficult. Here's what you need to do: o Decide what you want be STRICT compliant. The first step is to decide what you want to be STRICT compliant. STRICT is most valuable with newly developed code or code that you're maintaining or changing regularly. If you have a lot of stable code that has already been written and tested, and is not changed or maintained very often, you may decide that it's not worth the trouble to convert to STRICT. If you are writing a C++ application, you don't have the option of applying STRICT to only some of your source files. Because of the way C++ "type safe linking" works, you may get linking errors if you mix and match STRICT and non-STRICT source files in your application. o Enable strict compiler error checking - 6 - First, turn on all Borland C++'s warning and error messages. In the IDEs, you can use Options|Compiler| Messages|Display|All; for the command-line compiler, use the -w switch. Do this without turning on STRICT for now. In you're writing applications in C, you might want to compile them as C++ to take advantage of C++'s stricter type checking and type-safe linking. You can do this by renaming .C files to .CPP or by using the IDEs' Options|Compiler|C++ Options|C++ Always option or the command-line compiler's -p switch. o Change your code to use new STRICT types First you need to go through your own source and header files and change type declarations to use the new types defined in windows.h. Below are the common types that should be changed: ----------------------------------------------------- Old type New type(s) ----------------------------------------------------- HANDLE HINSTANCE, HMODULE, HGLOBAL, HLOCAL, etc. as appropriate WORD UINT (EXCEPT where you really want a 16-bit value even on a 32-bit platform) or WPARAM LONG LPARAM or LRESULT as appropriate FARPROC WNDPROC, DLGPROC, HOOKPROC, etc. as appropriate (MakeProcInstance and FreeProcInstance calls require casting) ----------------------------------------------------- See "Strict Conversion Notes" below for particular things to watch out for when changing your code. The UINT type is important for 32-bit Windows migration. On 16-bit windows, WORD and UINT are identical: 16 bit unsigned quantities. On a 32-bit platform, a UINT will be 32 bits and WORD is 16 bits. - 7 - This allows much more efficient code to be generated on the 32-bit platform where UINTs are used. You should use WORD in your code ONLY in those places where you want a 16 bit value, even on a 32-bit platform. Because C++ mangles function names, any callback C++ users note! function whose arguments have changed between Windows 3.0 and 3.1 (from WORD to UINT, for example) will generate link-time errors. You must either change the argument types or replace the 3.1 version of windows.h with the 3.0 version (in win30.h). It's highly recommended that you change the parameter types. You may also want to use CALLBACK instead of FAR PASCAL in the declaration of your various callback functions, though this isn't necessary. You may be able to save yourself some work by not converting the body of your window and dialog procedures to use WPARAM, LPARAM, and LRESULT right away, since those types are compatible with WORD and LONG, even in STRICT. o Make sure your functions are declared before use To compile as C++ or with all warnings enabled, all of your application functions must be properly declared before they are used. It's best to have all your declarations in an include file, rather than declaring them in your source files as needed: it's much easier to maintain your code this way should you need to change any of the declarations in the future. Chances are you will need to be making changes to the function declarations as you change over to the new STRICT data types. While it's not strictly necessary to do so, it's a good idea to provide function parameter names in your function prototypes. This makes header files much easier to read, and provides a degree of self-documentation. o Recompile without STRICT and fix resulting warnings Without defining STRICT anywhere, recompile your application and fix any warnings that result. You can - 8 - use the Borland C++ IDE's Search|Next Error (Alt+F7) command to speed up those fixes. Some common compiler warnings--and how you should deal with them--are described later in this section. Use the rules found there to make the appropriate changes to your source. o Run the app to make sure all is well. After you've gotten your application to compile cleanly as C++ or with all warnings enabled, it's a good idea to run your app and put it through it's paces to make sure all is well. o Define STRICT After you've made a first pass and gotten things to compile cleanly without STRICT, it's time to turn it on and make the next round of changes. If you've decided that you want to make your entire app STRICT, then the best and easiest thing to do is define STRICT in your makefile, by passing the -DSTRICT flag to the compiler or to use the Options| Compiler|Code Generation|Defines input box. If you want to do it on a per-source file basis, then simply define STRICT in the source file before you include windows.h. o Recompile and clean up resulting errors Once you've made the changes to your window and dialog procedures as outlined above, you're ready to recompile everything. After turning on STRICT you'll probably get new errors and warnings that you'll need to go through and clean up. You might also get link-time errors because of C++ users note! mismatched function parameter types. Check your function prototypes and definitions carefully. The list of warnings and errors below will explain how to deal with most of the problems that arise. - 9 - STRICT conversion ======================================================= hints 1. Always declare function pointers with the proper function type, rather than FARPROC. You'll need to cast function pointers to and from the proper function type when using MakeProcInstance, FreeProcInstance, and other functions that take or return a FARPROC: BOOL CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); DLGPROC lpfnDlg; lpfnDlg=(DLGPROC)MakeProcInstance(DlgProc, hinst); ... FreeProcInstance((FARPROC)lpfnDlg); 2. Take special care with HMODULEs and HINSTANCEs. For the most part, the Kernel module management functions use HINSTANCEs, but there are a few APIs that return or accept only HMODULEs. WinMain and 3. If you've copied any API function declarations from LibMain are two windows.h, they may have changed, and your local common examples. declaration may be out of date. Remove your local declaration. 4. Properly cast the results of LocalLock and GlobalLock to the proper kind of data pointer. Parameters to these and other memory management functions should be cast to LHANDLE or GHANDLE, as appropriate. 5. Properly cast the result of GetWindowWord and GetWindowLong and the parameters to SetWindowWord and SetWindowLong. 6. When casting SendMessage, DefWindowProc, and SendDlgItemMsg or any other function that returns an LRESULT or LONG to a handle of some kind, you must first cast the result to a UINT: HBRUSH hbr; hbr = (HBRUSH)(UINT)SendMessage(hwnd, WM_CTLCOLOR, - 10 - ..., ...); 7. The CreateWindow and CreateWindowEx hmenu parameter is sometimes used to pass an integer control ID. In this case you must cast this to an HMENU: HWND hwnd; int id; hwnd = CreateWindow("Button", "Ok", BS_PUSHBUTTON, x, y, cx, cy, hwndParent, (HMENU)id, //Cast required here hinst, NULL); 8. Polymorphic data types (WPARAM, LPARAM, LRESULT, void FAR *) should be assigned to variables of a known type as soon as possible. You should avoid using them in your own code when the type of the value is known. This will minimize the number of potentially unsafe and non-32-bit-portable casting you will have to do in your code. The macro APIs and message cracker mechanisms provided in windowsx.h will take care of almost all packing and unpacking of these data types, in a 32-bit portable way. 9. Become familiar with the common compiler warnings and errors that you're likely to encounter as you convert to STRICT. Common compiler ======================================================= warnings and errors Here are some common compiler warnings and errors you might get when trying to make your application compile cleanly as C++ or with all messages enabled, with or without STRICT. These are also the kinds of warnings and errors you'll receive as you maintain STRICT source code. Warning: Function should return a value This warning means that a function declared to return a value does not return a value. In older, non-ANSI C code, it was common to declare functions that did not return a value with no return type: foo(i) int i; - 11 - { ... } Functions declared in this manner are treated by the compiler as being declared to return an "int". If the function does not return anything, it should be declared "void": void foo(int i) { ... } Warning: Call to functionwith no prototype This means that a function was used before it was fully prototyped, or declared. It can also arise when a function that takes no arguments is not prototyped with void: void bar(); /* Should be: bar(void) */ main() { bar(); } Error: Lvalue required Error: Type mismatch in parameter These errors indicate that you are trying to assign or pass a non-pointer type when a pointer type is required. With STRICT defined, all handle types as well as LRESULT, WPARAM, and LPARAM are internally declared as pointer types, so trying to pass an int, WORD, or LONG as a handle will result in these errors. These errors should be fixed by properly declaring the non-pointer values you're assigning or passing. In the case of special constants such as (HWND)1 to indicate "insert at bottom" to the window positioning functions, you should use the new macro such as HWND_BOTTOM. Only in rare cases should you suppress a type mismatch error with a cast: This can often generate incorrect code. - 12 - Error: Type mismatch in parameter foo.c(335) : warning C4049: 'argument' : indirection to different types These warnings indicate that you are passing or assigning a pointer of the wrong type. This is the warning you get if you pass the wrong type of handle to a function. This is because under STRICT all handle types are defined as pointers to unique structures. To suppress these warnings, fix the type mismatch error in your code. Once again, it's dangerous to suppress these warnings with a cast, since there may be an underlying type error in your app. Error: Type mismatch in redeclaration of This error will result if you have inconsistent declarations of a variable, parameter, or function in your source code. Warning: Conversion may lose significant digits This warning results when a value is converted by the compiler, such as from LONG to int. You're being warned because you may lose information from this cast. If you're sure there are no information-loss problems, you can suppress this warning with the appropriate explicit cast to the smaller type. Warning: Non-portable pointer conversion This error results when you cast a near pointer or a handle to a 32-bit value such as LRESULT, LPARAM, LONG or DWORD. This warning almost always represents a bug, because the hi-order 16 bits of the value will contain a non-zero value. The compiler first converts the 16-bit near pointer to a 32-bit far pointer by placing the current data segment value in the high 16 bits, then converts this far pointer to the 32-bit value. To avoid this warning and ensure that a 0 is placed in the hi 16 bits, you must first cast the handle to a UINT: HWND hwnd; LRESULT result = (LRESULT)(UINT)hwnd; In cases where you DO want the 32-bit value to contain a far pointer, you can avoid the warning with an explicit cast to a far pointer: - 13 - char near* pch; LPARAM lParam = (LPARAM)(LPSTR)pch; Error: Size of the type is unknown or zero This error results from trying to change the value of a void pointer with + or +=. These typically result from the fact that certain Windows functions that return pointers to arbitrary types (such as GlobalLock and LocalLock) are defined to return void FAR* rather than LPSTR. To solve these problems, you should assign the void* value to a properly- declared variable (with the appropriate cast): BYTE FAR* lpb = (BYTE FAR*)GlobalLock(h); lpb += sizeof(DWORD); Error: Not an allowed type This error typically results from trying to dereference a void pointer. This usually results from directly using the return value of GlobalLock or LocalLock as a pointer. To solve this problem, assign the return value to a variable of the appropriate type (with the appropriate cast) before using the pointer: BYTE FAR* lpb = (BYTE FAR*)GlobalLock(h); *lpb = 0; Warning: Parameter is never used This message can result in callback functions when your code does not use certain parameters. You can either turn off this warning, or use the argsused pragma to suppress it. =========================================================================== Macro APIs and message crackers =========================================================================== The macro APIs, message crackers and control APIs are defined in the file windowsx.h. The new handle types, structures, and helper macros as well as the STRICT option are a part of the standard windows.h. - 14 - Macro APIs ======================================================= windowsx.h contains a number of new APIs implemented as macros that call other APIs. Generally they make your code both easier to read and write, and they can save you lots of typing. These macros are all portable to 32-bit Windows. void FAR* WINAPI GlobalAllocPtr(WORD flags, DWORD cb) Same as GlobalAlloc, except that it returns a far pointer directly. void FAR* WINAPI GlobalReAllocPtr(void FAR* lp, DWORD cbNew, WORD flags) Same as GlobalReAlloc, except that it takes and returns a far pointer. BOOL WINAPI GlobalFreePtr(void FAR* lp) Same as GlobalFree, except used with far pointer alloced (or realloced) with functions above. BOOL WINAPI GlobalLockPtr(void FAR* lp) Same as GlobalLock, except used with far pointer. BOOL WINAPI GlobalUnlockPtr(void FAR* lp) Same as GlobalUnlock, except used with far pointer. HMODULE WINAPI GetInstanceModule(HINSTANCE hInstance); Maps an instance handle to a module handle. void WINAPI UnlockResource(HGLOBAL hResource); Unlocks a global resource handle locked with LockResource. BOOL WINAPI DeletePen(HPEN hpen) Deletes a pen (with proper typecasting) HPEN WINAPI GetStockPen(int i); Returns one of the stock pens indicated by i (properly cast to HPEN). HPEN WINAPI SelectPen(HDC hdc, HPEN hpenSelect) Selects a pen and returns previously selected pen (with proper type casting). BOOL WINAPI DeleteBrush(HBRUSH hbr) - 15 - Deletes a brush (with proper typecasting) HBRUSH WINAPI GetStockBrush(int i); Returns one of the stock brushes indicated by i (properly cast to HBRUSH). HBRUSH WINAPI SelectBrush(HDC hdc, HBRUSH hbrSelect) Selects a brush and returns previously selected brush (with proper type casting). BOOL WINAPI DeleteFont(HFONT hfont) Deletes a font (with proper typecasting) HFONT WINAPI GetStockFont(int i); Returns one of the stock fonts indicated by i (properly cast to HFONT) HFONT WINAPI SelectFont(HDC hdc, HFONT hfontSelect) Selects a font and returns previously selected font (with proper type casting). BOOL WINAPI DeleteBitmap(HBITMAP hbm) Deletes a bitmap (with proper typecasting) HBITMAP WINAPI SelectBitmap(HDC hdc, HBITMAP hbmSelect) Selects a bitmap and returns previously selected bitmap (with proper type casting) BOOL WINAPI DeleteRgn(HRGN hrgn) Deletes a region (with proper typecasting) int WINAPI CopyRgn(HRGN hrgnDst, HRGN hrgnSrc); Copies hrgnSrc to hrgnDst. int WINAPI IntersectRgn(HRGN hrgnResult, HRGN hrgnA, HRGN hrgnB); Intersects hrgnA with hrgnB, setting hrgnResult to the result. int WINAPI SubtractRgn(HRGN hrgnResult, HRGN hrgnA, HRGN hrgnB); Subtracts hrgnB from hrgnA, setting hrgnResult to the result. int WINAPI UnionRgn(HRGN hrgnResult, HRGN hrgnA, HRGN hrgnB); Computes the union of hrgnA and hrgnB, setting hrgnResult to the result. int WINAPI XorRgn(HRGN hrgnResult, HRGN hrgnA, HRGN hrgnB); - 16 - XORs hrgnA with hrgnB, setting hrgnResult to the result. void WINAPI InsetRect(RECT FAR* lprc, int dx, int dy) Insets the edges of a rectangle by dx and dy. HINSTANCE WINAPI GetWindowInstance(HWND hwnd) Returns the instance handle associated with a window. DWORD WINAPI GetWindowStyle(HWND hwnd) Returns the window style of a window. DWORD WINAPI GetWindowExStyle(HWND hwnd) Returns the extended window style of a window int WINAPI GetWindowID(HWND hwnd) Returns the window ID of a window. void WINAPI SetWindowRedraw(HWND hwnd, BOOL fRedraw) Disables or enables drawing in a window, without hiding the window. WNDPROC WINAPI SubclassWindow(HWND hwnd, WNDPROC lpfnWndProc) Subclasses a window by storing a new window procedure address. Returns previous window procedure address. BOOL WINAPI IsMinimized(HWND hwnd) Returns TRUE if hwnd is minimized BOOL WINAPI IsMaximized(HWND hwnd) Returns TRUE if hwnd is maximized BOOL WINAPI IsRestored(HWND hwnd) Returns TRUE if hwnd is restored. BOOL WINAPI IsLButtonDown(void) Returns TRUE if the left mouse button is down. BOOL WINAPI IsRButtonDown(void) Returns TRUE if the right mouse button is down. BOOL WINAPI IsMButtonDown(void) Returns TRUE if the middle mouse button is down. - 17 - 3.1-only macro APIs ======================================================= void WINAPI MapWindowPoints(HWND hwndFrom, HWND hwndTo, POINT FAR* lppt, WORD cpt); Maps cpt points at *lppt from the coordinate system of hwndFrom to that of hwndTo. void WINAPI MapWindowRect(HWND hwndFrom, HWND hwndTo, RECT FAR* lprc) Maps a rectangle from the coordinate system of hwndFrom to that of hwndTo. Control message ======================================================= APIs New APIs have been added for use in dealing with the various controls. These APIs are implemented as macros that call SendMessage, and they take care of packing the various parameters into wParam and lParam and casting the return value as needed. These macros are fully portable to 32-bit Windows: The 32-bit versions will transparently take into account any differences in parameter packing on the 32-bit platform. These macros make your source code smaller and more readable. They're especially valuable with STRICT in order to prevent type errors and incorrect message parameter passing. There is a 1-to-1 correspondence between a control API and a window message or window manager API. In the interests of brevity, the control APIs are simply listed below: For more information, you can check out the macro definitions in windowsx.h and the documentation for the corresponding window message. Some of the new control APIs are usable with Windows 3.1 only, and are not available if you #define WINVER 0x0300. ------------------ Here is an example showing how these new APIs are used. Control API First, here is some code that uses old-style Examples SendMessage calls to print all the lines in an edit ------------------ control: - 18 - void PrintLines(HWND hwndEdit) { int line; int lineLast = (int)SendMessage(hwndEdit, EM_GETLINECOUNT, 0, 0L); for (line = 0; line < lineLast; line++) { int cch; char ach[80]; *((LPINT)ach) = sizeof(ach); cch = (int)SendMessage(hwndEdit, EM_GETLINE, line, (LONG)(LPSTR)ach); printf(ach); // ... or whatever ... } } Using control APIs, this code would be simplified as follows: void PrintLines(HWND hwndEdit) { int line; int lineLast = Edit_GetLineCount(hwndEdit); for (line = 0; line < lineLast; line++) { int cch; char ach[80]; cch = Edit_GetLine(hwndEdit, line, ach, sizeof(ach)); printf(ach); // ... or whatever ... } } The new style code is much easier to read (and write), doesn't generate compiler warnings, and doesn't have any non-portable casts. Here is a complete list of the control APIs. See windowsx.h for more info. Static_Enable(hwnd, fEnable) Static_GetText(hwnd, lpch, cchMax) Static_GetTextLength(hwnd) - 19 - Static_SetText(hwnd, lpsz) Static_SetIcon(hwnd, hIcon) Static_GetIcon(hwnd, hIcon) Button_Enable(hwnd, fEnable) Button_GetText(hwnd, lpch, cchMa Button_GetTextLength(hwnd) Button_SetText(hwnd, lpsz) Button_GetCheck(hwnd) Button_SetCheck(hwnd, check) Button_GetState(hwnd) Button_SetState(hwnd, state) Button_SetStyle(hwnd, style, fRedraw) Edit_Enable(hwnd, fEnable) Edit_GetText(hwnd, lpch, cchMax) Edit_GetTextLength(hwnd) Edit_SetText(hwnd, lpsz) Edit_LimitText(hwnd, cchMax) Edit_GetLineCount(hwnd) Edit_GetLine(hwnd, line, lpch, cchMax) Edit_GetRect(hwnd, lprc) Edit_SetRect(hwnd, lprc) Edit_SetRectNoPaint(hwnd, lprc) Edit_GetSel(hwnd) Edit_SetSel(hwnd, ichStart, ichEnd) Edit_ReplaceSel(hwnd, lpszReplace) Edit_GetModify(hwnd) Edit_SetModify(hwnd, fModified) Edit_LineFromChar(hwnd, ich) Edit_LineIndex(hwnd, line) Edit_LineLength(hwnd, line) Edit_Scroll(hwnd, dv, dh) Edit_CanUndo(hwnd) Edit_Undo(hwnd) Edit_EmptyUndoBuffer(hwnd) Edit_SetPasswordChar(hwnd, ch) Edit_SetTabStops(hwnd, cTabs, lpTabs) Edit_SetWordBreak(hwnd, lpfnWordBreak) Edit_FmtLines(hwnd, fAddEOL) Edit_GetHandle(hwnd) Edit_SetHandle(hwnd, h) Edit_GetFirstVisible(hwnd) ScrollBar_Enable(hwnd, flags) ScrollBar_Show(hwnd, fShow) ScrollBar_SetPos(hwnd, pos, fRedraw) ScrollBar_GetPos(hwnd) - 20 - ScrollBar_SetRange(hwnd, posMin, posMax, fRedraw) ScrollBar_GetRange(hwnd, lpposMin, lpposMax) ListBox_Enable(hwnd, fEnable) ListBox_GetCount(hwnd) ListBox_ResetContent(hwnd) ListBox_AddString(hwnd, lpsz) ListBox_InsertString(hwnd, lpsz, index) ListBox_AddItemData(hwnd, data) ListBox_InsertItemData(hwnd, lpsz, index) ListBox_DeleteString(hwnd, index) ListBox_GetTextLen(hwnd, index) ListBox_GetText(hwnd, index, lpszBuffer) ListBox_GetItemData(hwnd, index) ListBox_SetItemData(hwnd, index, data) ListBox_FindString(hwnd, indexStart, lpszFind) ListBox_FindItemData(hwnd, indexStart, data) ListBox_SetSel(hwnd, fSelect, index) ListBox_SelItemRange(hwnd, fSelect, first, last) ListBox_GetCurSel(hwnd) ListBox_SetCurSel(hwnd, index) ListBox_SelectString(hwnd, indexStart, lpszFind) ListBox_SelectItemData(hwnd, indexStart, data) ListBox_GetSel(hwnd, index) ListBox_GetSelCount(hwnd) ListBox_GetTopIndex(hwnd) ListBox_GetSelItems(hwnd, cItems, lpIndices) ListBox_SetTopIndex(hwnd, indexTop) ListBox_SetColumnWidth(hwnd, cxColumn) ListBox_GetHorizontalExtent(hwnd) ListBox_SetHorizontalExtent(hwnd, cxExtent) ListBox_SetTabStops(hwnd, cTabs, lpTabs) ListBox_GetItemRect(hwnd, index, lprc) ListBox_SetCaretIndex(hwnd, index) ListBox_GetCaretIndex(hwnd) ListBox_SetAnchorIndex(hwnd, index) ListBox_GetAnchorIndex(hwnd) ListBox_Dir(hwnd, attrs, lpszFileSpec) ListBox_AddFile(hwnd, lpszFilename) 3.1 only ListBox_SetItemHeight(hwnd, index, cy) 3.1 only ListBox_GetItemHeight(hwnd, index) ComboBox_Enable(hwnd, fEnable) ComboBox_GetText(hwnd, lpch, cchMax) ComboBox_GetTextLength(hwnd) ComboBox_SetText(hwnd, lpsz) ComboBox_LimitText(hwnd, cchLimit) - 21 - ComboBox_GetEditSel(hwnd) ComboBox_SetEditSel(hwnd, ichStart, ichEnd) ComboBox_GetCount(hwnd) ComboBox_ResetContent(hwnd) ComboBox_AddString(hwnd, lpsz) ComboBox_InsertString(hwnd, index, lpsz) ComboBox_AddItemData(hwnd, data) ComboBox_InsertItemData(hwnd, index, data) ComboBox_DeleteString(hwnd, index) ComboBox_GetLBTextLen(hwnd, index) ComboBox_GetLBText(hwnd, index, lpszBuffer) ComboBox_GetItemData(hwnd, index) ComboBox_SetItemData(hwnd, index, data) ComboBox_FindString(hwnd, indexStart, lpszFind) ComboBox_FindItemData(hwnd, indexStart, data) ComboBox_GetCurSel(hwnd) ComboBox_SetCurSel(hwnd, index) ComboBox_SelectString(hwnd, indexStart, lpszSelect) ComboBox_SelectItemData(hwnd, indexStart, data) ComboBox_Dir(hwnd, attrs, lpszFileSpec) ComboBox_ShowDropdown(hwnd, fShow) 3.1 only ComboBox_GetDroppedState(hwnd) 3.1 only ComboBox_GetDroppedControlRect(hwnd, lprc) 3.1 only ComboBox_GetItemHeight(hwnd) 3.1 only ComboBox_SetItemHeight(hwnd, cyItem) 3.1 only ComboBox_GetExtendedUI(hwnd) 3.1 only ComboBox_SetExtendedUI(hwnd, flags) Message cracker ======================================================= macros The message cracker macros provide a convenient, portable, and type-safe mechanism for dealing with window messages, their parameters, and their return values. The basic idea is that instead of having to pick apart message parameters with casts and HIWORD/LOWORD and such, you simply declare and implement a function that has the properly typed parameters and return value. The message crackers efficiently pick apart the message parameters, call your function, and return the appropriate value from the window message. Message forwarder macros allow you to forward a message via DefWindowProc, SendMessage, or CallWindowProc. The macros do the work of packing explicitly typed - 22 - arguments into wParam and lParam and calling the appropriate function. With these macros, you don't have to worry about what parameters go where and what kind of casting you need to do. They are also portable to 32-bit Windows (where some of the message parameters have changed). ------------------ For each window message, there are two macros: A Using message cracker and a forwarder. To see how these macros work, crackers and let's use the WM_CREATE message as an example. Here is forwarders a code fragment showing how a window procedure could ------------------ use message crackers to handle the WM_CREATE message. For now, our WM_CREATE message processing will simply call DefWindowProc. NOTE: The following examples use STRICT-style declarations, but you can use message crackers without STRICT. // Message handler function prototype (declared in a .h file) BOOL MyCls_OnCreate(HWND hwnd, CREATESTRUCT FAR* lpCreateStruct); // Window procedure for class "MyCls" (defined in a .c file) LRESULT _export CALLBACK MyCls_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: return HANDLE_WM_CREATE(hwnd, wParam, lParam, MyCls_OnCreate); default: return DefWindowProc(hwnd, msg, wParam, lParam); } } // WM_CREATE message handler function. // For now, just calls DefWindowProc. BOOL MyCls_OnCreate(HWND hwnd, CREATESTRUCT FAR* lpCreateStruct) { return FORWARD_WM_CREATE(hwnd, lpCreateStruct, DefWindowProc); - 23 - } Some important points: o You must declare and implement a function to handle the message, which must have a particular "signature" (the order and type of the parameters, and the type of the return value, if any). o You pass this message handler function as the last parameter to the HANDLE_WM_XXX function. This function must be declared and fully prototyped before being used with a message cracker. o You must always return the value returned by the HANDLE_WM_XXX function, even if the message handler function is declared void. o The FORWARD_WM_XXX function always has the same signature (parameters and return type) as its corresponding message handler function, with the addition of the last parameter, which is the API or function to be used to forward the message. o You don't need to use message crackers to handle all your messages. Old-style message handling code can be mixed with message crackers in the same window procedure. o By convention, message handler functions are named "Class_OnXXX", where Class is the window class name and XXX is the name of the corresponding message, minus the "WM_" and using mixed case instead of all caps. This is just a convention: you can use any name you like for the function (although you can't alter its signature). ------------------ The HANDLE_MSG macro can be used to reduce the amount Saving time and of "noise" in your window procedures and save yourself improving some typing. The HANDLE_MSG macro replaces the "case readability with WM_XXX:", the HANDLE_WM_XXX call, and the return. So, HANDLE_MSG instead of ------------------ case WM_CREATE: return HANDLE_WM_CREATE(hwnd, wParam, lParam, MyCls_OnCreate); - 24 - you can just type: HANDLE_MSG(hwnd, WM_CREATE, MyCls_OnCreate); HANDLE_MSG is optional. Some people prefer to "spell out" the goings-on in their window procedures, and others prefer the convenience and brevity of the HANDLE_MSG form. HANDLE_MSG requires that you name your window procedure message parameters wParam and lParam. The examples in this document use HANDLE_MSG. ------------------ To see how message crackers work, we'll take a look at How message the definition of the message cracker macros as found crackers work in windowsx.h (these definitions are somewhat ------------------ simplified for the sake of clarity): // BOOL Cls_OnCreate(HWND hwnd, CREATESTRUCT FAR* lpCreateStruct) #define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \ ((fn)(hwnd, (CREATESTRUCT FAR*)lParam) ? 0L : (LRESULT)-1L) #define FORWARD_WM_CREATE(hwnd, lpCreateStruct, fn) \ (BOOL)(DWORD)(fn)(hwnd, WM_CREATE, 0, (LPARAM)lpCreateStruct) The comment shows the signature of the message handler function that you must declare and implement. Essentially all these macros do is convert the wParam and lParam message parameters to and from specific, explicitly typed parameters and invoke a function. The HANDLE_WM_CREATE macro calls the handler function with the appropriate parameters, obtained by casting wParam and lParam appropriately. The return value of the handler value is mapped to the proper LRESULT return value, in this case 0L or -1L. If the handler function returns no value, then 0L is returned. The FORWARD_WM_CREATE macro calls the supplied message function (which must have the same signature as DefWindowProc) with the proper hwnd, wParam, and lParam parameters calculated from the parameters supplied to the macro. The HANDLE_MSG macro is quite simple too: - 25 - #define HANDLE_MSG(hwnd, message, fn) \ case message: return HANDLE_##message(fn, hwnd, wParam, lParam) It simply does the "case" for you, and returns the result of the proper HANDLE_WM_XXX function. The message parameter names wParam and lParam are hard-wired into this macro. ------------------ Here is a more detailed example of a window procedure Message cracker for the "Template" class that uses message crackers and examples message forwarders: ------------------ // Excerpt from header file for class Template // Window procedure prototype LRESULT _export CALLBACK Template_WndProc(HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam) // Default message handler #define Template_DefProc DefWindowProc // Template class message handler functions, // declared in a header file: void Template_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); void Template_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags); void Template_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags); HBRUSH Template_OnCtlColor(HWND hwnd, HDC hdc, HWND hwndChild, int type); // Exerpt from c source file for class Template // Template window procedure implementation. LRESULT _export CALLBACK Template_WndProc(HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam) { switch (msg) { - 26 - HANDLE_MSG(hwnd, WM_MOUSEMOVE, Template_OnMouseMove); HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Template_OnLButtonDown); HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK, Template_OnLButtonDown); HANDLE_MSG(hwnd, WM_LBUTTONUP, Template_OnLButtonUp); default: return Template_DefProc(hwnd, msg, wParam, lParam); } } // Message handler function implementations: void Template_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags) { ... } void Template_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { ... } void Template_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) { ... } HBRUSH Template_OnCtlColor(HWND hwnd, HDC hdc, HWND hwndChild, int type) { switch (type) { case CTLCOLOR_BTN: // Pass the WM_CTLCOLOR message on to the parent, // and use the edit control colors. return FORWARD_WM_CTLCOLOR(GetParent(hwnd), hdc, hwndChild, CTLCOLOR_EDIT, SendMessage); break; default: // Perform default processing of the message. return FORWARD_WM_CTLCOLOR(hwnd, hdc, hwndChild, type, Template_DefProc); } } - 27 - ------------------ For the most part, the OnXXX message handler function Message handler parameters have the same name and type as those shown function in the documentation of the corresponding window signatures message. To find out exactly what those messages are, ------------------ look at the commented function prototype in windowsx.h before the message cracker for the message you're interested in. There are a few cases where the OnXXX functions work a bit differently than the corresponding window message: o OnCreate (and OnNCCreate) must return TRUE if all is well, or the window will not be created and CreateWindow will return NULL. o The signatures for OnKey and On?ButtonDown functions are a little different from their corresponding messages. OnKey handles both key up and key down messages with the fDown parameter. The On?ButtonDown functions handle double click messages too, with the fDoubleClick parameter (though you must be sure to register your window class with CS_DBLCLKS if you want to handle double clicks). o The OnChar function is not passed the virtual key or key flags information, as this information is not usable in a WM_CHAR handling. This is because different virtual keys can generate the same WM_CHAR messages, and certain key macro processors will generate WM_CHAR messages with no virtual key or flags. ------------------ For every window class, there is a function that must Improving be called to perform the default processing for that reusability: window. Normally, this call is DefWindowProc, but if Template_DefProc you're subclassing a window, it's CallWindowProc, or if ------------------ you're implementing an MDI child window, it's DefMDIChildProc, etc. A very common programming mistake is to copy code from another window procedure without making the appropriate change to the default message handler function. This can lead to subtle, hard to track down bugs. - 28 - This is the purpose of the Template_DefProc macro defined and used in the example above. Every class should have an appropriate XXX_DefProc macro (or function) defined which will perform default message processing. In the example above, the default message handler is DefWindowProc, so Template_DefProc is defined as follows: #define Template_DefProc DefWindowProc. For an MDI child window, it might be: #define MdiWnd_DefProc DefMDIChildProc The advantage of this scheme is that to steal code from another window procedure, you need only change the class name prefix, and the proper default handling will be taken care of automatically. ------------------ Message crackers and forwarders work well with new Private and window messages that you define. You must write a registered window message cracker and forwarder macro for the new message messages -- the easiest way to do this is to copy and modify ------------------ existing macros from windowsx.h. If your new message value is a constant (e.g., WM_USER+100), then you can use HANDLE_MSG to handle the message in your window procedure. However, if your new message is registered with RegisterWindowMessage, HANDLE_MSG can't be used, because variables cannot be used as switch statement case values: only constants can. In this case, you can handle it as follows: // In Template class initialization code: UINT WM_NEWMESSAGE = 0; WM_NEWMESSAGE = RegisterWindowMessage("WM_NEWMESSAGE"); ... // In Template_WndProc: window procedure: LRESULT _export CALLBACK Template_WndProc(HWND hwnd, WORD msg, WPARAM wParam, - 29 - LPARAM lParam) { if (msg == WM_NEWMESSAGE) HANDLE_WM_NEWMESSAGE(hwnd, wParam, lParam, Template_OnNewMessage); switch (msg) { HANDLE_MSG(hwnd, WM_MOUSEMOVE, Template_OnMouseMove); ... } } ------------------ It's very common for a window to have some additional Message crackers "instance data" associated with it that is kept in a and window separate data structure allocated by the application. instance data This separate data structure is associated with its ------------------ corresponding window by storing a pointer to the structure in a specially-named window property or in a window word (allocated by setting the cbWndExtra field of the WNDCLASS structure when the class is registered). The message crackers fully support this style of programming by allowing you to pass a pointer to the instance data as the first parameter to the message handlers instead of a window handle. The following example should make this clear: // Window instance data structure. // Must include window handle field. typedef struct _FOO { HWND hwnd; int otherStuff; } FOO; // "Foo" window class was registered with // cbWndExtra = sizeof(FOO*), so we can use // a window word to store back pointer. // Window properties can also be used. // These macros get and set the hwnd -> FOO* backpointer. // Use GetWindowWord or GetWindowLong as appropriate based // on the default size of data pointers. - 30 - #if (defined(__SMALL__) | defined(__MEDIUM__)) #define Foo_GetPtr(hwnd) \ (FOO*)GetWindowWord((hwnd), 0) #define Foo_SetPtr(hwnd, pfoo) \ (FOO*)SetWindowWord((hwnd), 0, (WORD)(pfoo)) #else #define Foo_GetPtr(hwnd) \ (FOO*)GetWindowLong((hwnd), 0) #define Foo_SetPtr(hwnd, pfoo) \ (FOO*)SetWindowLong((hwnd), 0, (LONG)(pfoo)) #endif // Default message handler #define Foo_DefProc DefWindowProc // Message handler functions, declared with a FOO* as their // first argument, rather than an HWND. Other than that, // their signature is identical to that shown in windowsx.h. BOOL Foo_OnCreate(FOO* pfoo, CREATESTRUCT FAR* lpcs); void Foo_OnPaint(FOO* pfoo); // Code to register the Foo window class: BOOL Foo_Init(HINSTANCE hinst) { WNDCLASS cls; cls.hCursor = ...; cls.hIcon = ...; cls.lpszMenuName = ...; cls.hInstance = hinst; cls.lpszClassName = "Foo"; cls.hbrBackground = ...; cls.lpfnWndProc = Foo_WndProc; cls.style = CS_DBLCLKS; cls.cbWndExtra = sizeof(FOO*); // room for instance // data ptr cls.cbClsExtra = 0; return RegisterClass(&cls); } // The window procedure for class "Foo". This demonstrates how // instance data is attached to a window and passed to the // message handler functions. It's fully STRICT enabled, // and Win 3.0 compatible. - 31 - LRESULT CALLBACK _export Foo_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { FOO* pfoo = Foo_GetPtr(hwnd); if (pfoo == NULL) { // If we're creating the window, then try to allocate it. if (msg == WM_NCCREATE) { // Create the instance data structure, set up the hwnd // backpointer field, and associate it with the window. pfoo = (FOO*)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(FOO)); // If an error occured, return 0L to fail the CreateWindow // call. This will cause CreateWindow to return NULL. if (pfoo == NULL) return 0L; pfoo->hwnd = hwnd; Foo_SetPtr(hwnd, pfoo); // NOTE: the rest of the FOO structure should be // initialized inside of Template_OnCreate // (or Template_OnNCCreate). // Further creation data may be accessed through the // CREATESTRUCT FAR* parameter. } else { // It turns out WM_NCCREATE is NOT necessarily the first // message recieved by a top-level window // (WM_GETMINMAXINFO is). // Pass messages that precede WM_NCCREATE on through to // Foo_DefProc return Foo_DefProc(hwnd, msg, wParam, lParam); } } if (msg == WM_NCDESTROY) { // The window is being destroyed: free up the FOO structure. - 32 - // NOTE: If you want to handle WM_NCDESTROY with a message // cracker (NOT RECOMMENDED), you can uncomment the lines // below. // LRESULT result = HANDLE_MSG(hwnd, WM_NCDESTROY, Client_OnNCDestroy); // Deallocation of any fields of the FOO structure should // be done inside the OnNCDestroy function. LocalFree((HLOCAL)pfoo); pfoo = NULL; Foo_SetPtr(hwnd, NULL); //return result; } switch (msg) { HANDLE_MSG(pfoo, WM_CREATE, Foo_OnCreate); HANDLE_MSG(pfoo, WM_PAINT, Foo_OnPaint); ... default: return Foo_DefProc(hwnd, msg, wParam, lParam); } } ------------------ Dialog procedures are different from window procedures Message crackers in that they return a BOOL indicating whether the and dialog message was processed rather than an LRESULT. For this procedures reason, you can't use HANDLE_MSG: You must invoke the ------------------ message cracker macro explicitly. Here's an example that shows how you'd use message crackers in a dialog procedure: BOOL MyDlg_OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam); void MyDlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify); BOOL _export CALLBACK MyDlg_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) - 33 - { switch (msg) { // Since HANDLE_WM_INITDIALOG returns an LRESULT, // we must cast it to a BOOL before returning. case WM_INITDIALOG: return (BOOL)HANDLE_WM_INITDIALOG(hwndDlg, wParam, lParam, MyDlg_OnInitDialog); case WM_COMMAND: HANDLE_WM_COMMAND(hwndDlg, wParam, lParam, MyDlg_OnCommand); return TRUE; break; default: return FALSE; } } If you'd like to process messages that return values such as WM_ERASEBKGND in your dialog procedure, or would like to make it easier to share code between your window procedures and your dialog procedures, you may want to make use of the techniques shown later in "Dialog Procedures: A Better Way." ------------------ Message crackers can also be used to simplify window Message crackers subclassing code. With message crackers, unprocessed and window messages must be forwarded using the appropriate subclassing FORWARD_WM_* macro. When you are subclassing a window, ------------------ the proper way to forward unprocessed messages is by calling CallWindowProc, passing it the previous window procedure address, along with the four standard window message parameters. The FORWARD_WM_* macros can't be used directly with CallWindowProc, because they can only invoke functions having the standard window procedure signature: CallWindowProc has an extra WNDPROC parameter. This problem is handled easily by simply declaring XXX_DefProc as a function instead of a macro. Your function must call CallWindowProc instead of calling DefWindowProc: - 34 - Here's an example showing how this works: // Global variable that holds the previous window // procedure address of the subclassed window: WNDPROC Foo_lpfnwpDefProc = NULL; // Code fragment to subclass a window // and store previous wndproc value: void SubclassFoo(HWND hwndFoo) { // Global application instance handle: extern HINSTANCE g_hinstFoo; ... // SubclassWindow is a macro API that calls SetWindowLong // as appropriate to change the window procedure of hwndFoo. Foo_lpfnwpDefProc = SubclassWindow(hwndFoo, (WNDPROC)MakeProcInstance ((FARPROC)Foo_WndProc, g_hinstFoo)); ... } // Default message handler function // This function invokes the superclasses' window procedure. // It must be declared with the same signature as any window // procedure, so it can be used with the FORWARD_WM_* macros. LRESULT Foo_DefProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return CallWindowProc(Foo_lpfnwpDefProc, hwnd, msg, wParam, lParam); } // Foo window procedure. Everything here is the same as in the // normal non-subclassed case: the differences are encapsulated // in Foo_DefProc. LRESULT CALLBACK Foo_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { HANDLE_MSG(hwnd, WM_CHAR, Foo_OnChar); - 35 - ... default: // Be sure to call Foo_DefProc, NOT DefWindowProc! return Foo_DefProc(hwnd, msg, wParam, lParam); } } // Message handlers void Foo_OnChar(HWND hwnd, UINT ch, int cRepeat) { if (ch == ... || whatever) { // handle it here } else { // Forward the message on to Foo_DefProc FORWARD_WM_CHAR(hwnd, ch, cRepeat, Foo_DefProc); } } ------------------ There are two longstanding sources of confusion and Dialog procedures: bugs in Windows dialog procedures: 1. There is no way A better way to return a value from a message handled by a dialog ------------------ procedure, and 2. it's not possible to execute the default dialog behavior for a message before executing your own code in your dialog procedure. Here is a simple solution to both of these problems that is compatible with both Windows 3.0 and 3.1. It unifies the way window procedures and dialog procedures are coded, and it works very nicely with message crackers. Note These techniques, like message crackers, are completely optional. You can use these techniques with or without message crackers. You can code a dialog procedure just as if it were a window procedure: you have the same freedom to return values and execute the default dialog processing messages as you do with window procedures. It's also easier to copy or share code between dialog and window procedures. - 36 - Executing default dialog procedure functionality ======================================================= Although Windows provides the DefDlgProc API, it can't be used as is for our purposes because its implementation calls the dialog procedure again. If we called it from our dialog procedure, the dialog procedure would be called again, recursively, until we run out of stack space and crash. To prevent this infinite recursion, we need only detect that we're being called recursively and return FALSE, which will cause the default processing to be performed. Returning message results from dialog procedures ======================================================= Windows 3.0 and 3.1 both support a general mechanism for returning values from messages handled in dialog procedures. Essentially, you store the return value with SetWindowLong, which will get returned from the message when your dialog procedure returns TRUE. There are some special cases you have to worry about: in some cases, the return value must be returned in place of the BOOL return value. How it works ======================================================= Here is some code that shows how all this comes together: // function prototypes in header file.. BOOL CALLBACK _export MyDlg_OldProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT MyDlg_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); // implementation in .c file.. // static (or global) variable for preventing infinite recursion - 37 - static BOOL fMyDlgRecurse = FALSE; BOOL CALLBACK _export MyDlg_OldProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); { LRESULT result; // Check for possible recursion. If so, just return FALSE // after clearing the recursion flag, to ensure that the // default processing is executed. if (fMyDlgRecurse) { fMyDlgRecurse = FALSE; return FALSE; } result = MyDlg_DlgProc(hwndDlg, msg, wParam, lParam); // Here if we handled the message, and want to return result. switch (msg) { // The following messages are special-cased by the dialog // manager, and assumed to be returned as a BOOL from the // dialog procedure: case WM_INITDIALOG: case WM_CTLCOLOR: case WM_COMPAREITEM: case WM_VKEYTOITEM: case WM_CHARTOITEM: case WM_QUERYDRAGICON: return (BOOL)LOWORD(result); default: // All other messages use the DWL_MSGRESULT window words: SetWindowLong(hwndDlg, DWL_MSGRESULT, (LPARAM)result); return TRUE; } } LRESULT MyDlg_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); { switch (msg) { - 38 - HANDLE_MSG(hwndDlg, WM_INITDIALOG, MyDlg_OnInitDialog); HANDLE_MSG(hwndDlg, WM_COMMAND, MyDlg_OnCommand); default: // Call DefDlgProc to invoke default dialog processing // for messages we don't handle ourself. Set recursion // flag before we go, so MyDlg_OldDlgProc knows to // return FALSE. fMyDlgRecurse = TRUE; return DefDlgProc(hwndDlg, msg, wParam, lParam); } } You must declare a static (or global) BOOL variable that is initialized to FALSE. It's safe to use the same global variable for all your dialogs, even if one dialog procedure brings up another dialog box. What's important is that the same boolean variable be used in the "OldDlgProc" and before the call to DefDlgProx: a local BOOL variable must NOT be used, or infinite recursion will result. A simplified example using predefined macro APIs ======================================================= Three macro APIs are defined in windowsx.h that drastically simplify the code shown above. They are SetDlgMsgResult, DefDlgProcEx, and CheckDefDlgRecursion. Here's the same dialog code, this time using these macro APIs: // prototypes.. BOOL CALLBACK _export MyDlg_OldProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT MyDlg_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); // implementation.. - 39 - static BOOL fDefDlgEx = FALSE; BOOL CALLBACK _export MyDlg_OldProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); { CheckDefDlgRecursion(&fDefDlgEx); return SetDlgMsgResult(hwndDlg, msg, MyDlg_DlgProc(hwndDlg, msg, wParam, lParam)); } LRESULT MyDlg_DefProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); { return DefDlgProcEx(hwnd, msg, wParam, lParam, &fDefDlgEx); } LRESULT MyDlg_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); { switch (msg) { HANDLE_MSG(hwndDlg, WM_INITDIALOG, MyDlg_OnInitDialog); HANDLE_MSG(hwndDlg, WM_COMMAND, MyDlg_OnCommand); default: return MyDlg_DefProc(hwnd, msg, wParam, lParam); } } As mentioned earlier, it's safe to use the same boolean fDefDlgEx variable for all your dialog procedures, or you can define one for each of your dialogs. What's important is that the same boolean variable be used for the CheckDefDlgRecursion call AND the DefDlgProcEx call in a given dialog procedure: a local BOOL variable must NOT be used, or infinite recursion will result. You can also use the same _DefProc function declaration for all of your dialog procedures that use the same fDefDlgEx variable: for example, you could implement the following function: LRESULT CommonDlg_DefProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { return DefDlgProcEx(hwnd, msg, wParam, lParam, &fDefDlgEx); - 40 - } then, for each dialog class, #define something like: #define MyDlg_DefProc CommonDlg_DefProc ------------------ Converting existing code over to use message crackers Converting is not particularly difficult. Here are some existing code to suggestions that should help: use message crackers o Have a look at the sample app MAKEAPP for some more ------------------ detailed examples of the use of message crackers and message forwarders. o It's a good idea (though not necessary) to first convert your code to use STRICT. With STRICT enabled, the compiler can help you find parameter type mismatch and other errors much easier. o It's best to declare all your message handler functions in an include file, rather than declaring them directly in the .c file that uses them. You don't have to use the ClassName_OnXXX naming convention for your function, but we've found that it's quite a helpful way to organize the code for a window class. o When declaring or implementing a message forwarder function, just use your editor to search for and copy the message handler function prototype comment from windowsx.h and paste it into your source code. This way you don't have to type it from scratch. o Use the FORWARD_WM_* macros to send or forward non-control messages to other windows by passing SendMessage as the first parameter. o If you have defined your own private window messages, you should define a message cracker and forwarder for each. This is pretty simple to do: Just copy an existing cracker and forwarder from windowsx.h and edit it. Be sure to fully parenthesize your use of macro parameters, and be careful with the casts you use. Converting existing window and dialog procedures to message crackers can be a fair amount of work, - 41 - especially if the window and dialog procedures are large. You can mix and match old-style message handlers with message crackers, so you may want to use message crackers for new message handlers, or for existing code you plan on modifying extensively. Converting dialog procedures over to the new-style LRESULT-returning dialog procedures is also something that isn't required for all your dialog procedures. You can convert those that you plan to modify, or that contain code that you may want to reuse in other dialog procedures. - 42 - Reference: 7/2/98 10:42:51 AM
Article originally contributed by
Tags: C++Builder