All of the sample programs include the CmnHdr.h header file before any other header file. I wrote CmnHdr.h, which is listed in Figure A-1, to make my life a little easier. The file contains macros, linker directives, and other code that is common across all the applications. When I want to try something, all I do is modify CmnHdr.h and rebuild all the sample applications. CmnHdr.h is in the root directory on the companion CD-ROM.
The remainder of this appendix discusses each section of the CmnHdr.h header file. I'll explain the rationale for each section and describe how and why you might want to make changes before rebuilding all the sample applications.
Because some of the sample applications call functions that are new in Microsoft Windows 2000, this section of CmnHdr.h defines the _WIN32_WINNT symbol as follows:
#define _WIN32_WINNT 0x0500 |
I have to do this because the new Windows 2000 functions are prototyped in the Windows header files like this:
#if (_WIN32_WINNT >= 0x0500) WINBASEAPI BOOL WINAPI AssignProcessToJobObject( IN HANDLE hJob, IN HANDLE hProcess ); #endif /* _WIN32_WINNT >= 0x0500 */ |
Unless you specifically define _WIN32_WINNT as I have (before including Windows.h), the prototypes for the new functions will not be declared and the compiler will generate errors if you attempt to call these functions. Microsoft has protected these functions with the _WIN32_WINNT symbol to help ensure that applications you develop can run on multiple versions of Microsoft Windows NT and Windows 98.
I wrote all the sample applications so that they can be compiled as either ANSI or Unicode. When you compile the applications for the x86 CPU architecture, ANSI is the default so that the applications will execute on Windows 98. However, Unicode is used when you build the applications for any other CPU architecture so the applications will use less memory and execute faster.
To create Unicode versions for the x86 architecture, you simply uncomment the single line that defines UNICODE and rebuild. By defining the UNICODE macro in CmnHdr.h, you can easily control how you build the sample applications. For more information on Unicode, see Chapter 2.
When I develop software, I always try to ensure that the code compiles free of errors and warnings. I also like to compile at the highest possible warning level so that the compiler does the most work for me and examines even the most minute details of my code. For the Microsoft C/C++ compilers, this means that I built all the sample applications using warning level 4.
Unfortunately, Microsoft's operating systems group doesn't share my sentiments about compiling using warning level 4. As a result, when I set the sample applications to compile at warning level 4, many lines in the Windows header files cause the compiler to generate warnings. Fortunately, these warnings do not represent problems in the code. Most are generated by unconventional uses of the C language that rely on compiler extensions that almost all vendors of Windows-compatible compilers implement.
In this section of CmnHdr.h, I make sure that the warning level is set to 3 and that CmnHdr.h includes the standard Windows.h header file. Once Windows.h is included, I set the warning level to 4 when I compile the rest of the code. At warning level 4, the compiler emits "warnings" for things that I don't consider problems, so I explicitly tell the compiler to ignore certain benign warnings by using the #pragma warning directive.
When I work on code, I often like to get something working immediately and then make it bulletproof later. To remind myself that some code needs additional attention, I used to include a line like this:
#pragma message("Fix this later") |
When the compiler compiled this line, it would output a string reminding me that I had some more work to do. This message was not that helpful, however. I decided to find a way for the compiler to output the name of the source code file and the line number that the pragma appears on. Not only would I know that I had additional work to do, but I could also locate the surrounding code immediately.
To get this behavior, you have to trick the pragma message directive using a series of macros. The result is that you can use the chMSG macro like this:
#pragma chMSG(Fix this later) |
When the line above is compiled, the compiler produces a line that looks like this:
C:\CD\CmnHdr.h(82):Fix this later |
Now, using Microsoft Visual Developer Studio, you can double-click on this line in the output window and be automatically positioned at the correct place in the correct file.
As a convenience, the chMSG macro does not require quotes to be used around the text string.
I frequently use these two handy macros in my applications. The first one, chINRANGE, checks to see whether a value is between two other values. The second macro, chDIMOF, simply returns the number of elements in an array. It does this by using the sizeof operator to first calculate the size of the entire array in bytes. It then divides this number by the number of bytes required for a single entry in the array.
All the multithreaded samples in this book use the _beginthreadex function, which is in Microsoft's C/C++ run-time library, instead of the operating system's CreateThread function. I use this function because the _beginthreadex function prepares the new thread so that it can use the C/C++ run-time library functions and because it ensures that the per-thread C/C++ run-time library information is destroyed when the thread returns. (See Chapter 6 for more details.) Unfortunately, the _beginthreadex function is prototyped as follows:
unsigned long _ _cdecl _beginthreadex( void *, unsigned, unsigned (_ _stdcall *)(void *), void *, unsigned, unsigned *); |
Although the parameter values for _beginthreadex are identical to the parameter values for the CreateThread function, the parameters' data types do not match. Here is the prototype for the CreateThread function:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(PVOID pvParam); HANDLE CreateThread( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadId); |
Microsoft did not use the Windows data types when creating the _beginthreadex function's prototype because Microsoft's C/C++ run-time group does not want to have any dependencies on the operating system group. I commend this decision; however, this makes using the _beginthreadex function more difficult.
There are really two problems with the way Microsoft prototyped the _beginthreadex function. First, some of the data types used for the function do not match the primitive types used by the CreateThread function. For example, the Windows data type DWORD is defined as follows:
typedef unsigned long DWORD; |
This data type is used for CreateThread's cbStack parameter as well as for its fdwCreate parameter. The problem is that _beginthreadex prototypes these two parameters as unsigned, which really means unsigned int. The compiler considers an unsigned int to be different from an unsigned long and generates a warning. Because the _beginthreadex function is not a part of the standard C/C++ run-time library and exists only as an alternative to calling the CreateThread function, I believe that Microsoft should have prototyped _beginthreadex this way so that warnings are not generated:
unsigned long _ _cdecl _beginthreadex( void *psa, unsigned long cbStack, unsigned (_ _stdcall *) (void *pvParam), void *pvParam, unsigned long fdwCreate, unsigned long *pdwThreadId); |
The second problem is just a small variation of the first. The _beginthreadex function returns an unsigned long representing the handle of the newly created thread. An application typically wants to store this return value in a data variable of type HANDLE as follows:
HANDLE hThread = _beginthreadex(...); |
The code above causes the compiler to generate another warning. To avoid the compiler warning, you must rewrite the line above, introducing a cast as follows:
HANDLE hThread = (HANDLE) _beginthreadex(...); |
Again, this is inconvenient. To make life a little easier, I defined a chBEGINTHREADEX macro in CmnHdr.h to perform all of this casting for me:
typedef unsigned (_ _stdcall *PTHREAD_START) (void *); #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \ pvParam, fdwCreate, pdwThreadId) \ ((HANDLE)_beginthreadex( \ (void *) (psa), \ (unsigned) (cbStack), \ (PTHREAD_START) (pfnStartAddr), \ (void *) (pvParam), \ (unsigned) (fdwCreate), \ (unsigned *) (pdwThreadId))) |
I sometimes want to force a breakpoint in my code even if the process is not running under a debugger. You can do this in Windows by having a thread call the DebugBreak function. This function, which resides in Kernel32.dll, lets you attach a debugger to the process. Once the debugger is attached, the instruction pointer is positioned on the CPU instruction that caused the breakpoint. This instruction is contained in the DebugBreak function in Kernel32.dll, so to see my source code I must single-step out of the DebugBreak function.
On the x86 architecture, you perform a breakpoint by executing an "int 3" CPU instruction. So, on x86 platforms, I redefine DebugBreak as this inline assembly language instruction. When my DebugBreak is executed, I do not call into Kernel32.dll; the breakpoint occurs right in my code and the instruction pointer is positioned to the next C/C++ language statement. This just makes things a little more convenient.
When you work with software exceptions, you must create your own 32-bit exception codes. These codes follow a specific format (discussed in Chapter 24). To make creating these codes easier, I use the MAKESOFTWAREEXCEPTION macro.
The chMB macro simply displays a message box. The caption is the full pathname of the executable file for the calling process.
To find potential problems as I developed the sample applications, I sprinkled chASSERT macros throughout the code. This macro tests whether the expression identified by x is TRUE and, if it isn't, displays a message box indicating the file, line, and the expression that failed. In release builds of the applications, this macro expands to nothing. The chVERIFY macro is almost identical to the chASSERT macro except that the expression is evaluated in release builds as well as in debug builds.
When you use message crackers with dialog boxes, you should not use the HANDLE_MSG macro from Microsoft's WindowsX.h header file because it doesn't return TRUE or FALSE to indicate whether a message was handled by the dialog box procedure. My chHANDLE_DLGMSG macro massages the window message's return value and handles it properly for use in a dialog box procedure.
Because most of the sample applications use a dialog box as their main window, you must change the dialog box icon manually so that it is displayed correctly on the Taskbar, in the task switch window, and in the application's caption itself. The chSETDLGICONS macro is always called when dialog boxes receive a WM_INITDIALOG message so that the icons are set correctly.
Most of the sample applications run on all platforms, but some of them require features that are not supported in Windows 95 and Windows 98; some of them require features that are present only in Windows 2000. Each application checks the version of the host system when initializing and displays a notice if a more capable operating system is required.
For sample applications that do not run on Windows 95 and Windows 98, you'll see a call to my chWindows9xNotAllowed function in the application's _tWinMain function. For sample applications that require Windows 2000, you'll see a call to my chWindows2000Required function in the application's _tWinMain function.
Windows 98 does not support Unicode as completely as does Windows 2000. In fact, applications that call Unicode functions do not run on Windows 98! Unfortunately, Windows 98 does not give any notification if an application compiled for Unicode is invoked. For the applications in this book, this means that the applications start and terminate with no indication that they ever attempted to execute.
This drove me absolutely nuts! I needed a way to know that my application was built for Unicode but was running on a Windows 98 system, so I created a CUnicodeSupported C++ class. This class's constructor simply checks to see whether the host system has good Unicode support; if not, a message box is displayed and the process terminates.
You'll notice that in CmnHdr.h, I create a global, static instance of this class. When my application starts, the C/C++ run-time library startup code calls this object's constructor. If the constructor detects that the operating system has full Unicode support, it returns and the application continues running. By creating a global instance of this class, I did not have to put any special code in each sample application's source code modules. For non-Unicode builds, I do not declare or instantiate the C++ class. This allows the application to just run.
Some readers of previous editions of this book who added my source code modules to a new Visual C++ project received linker errors when building the project. The problem was that they created a Win32 Console Application project, causing the linker to look for a (w)main entry-point function. Since all of the book's sample applications are GUI applications, my source code has a _tWinMain entry-point function instead; this is why the linker complained.
My standard reply to readers was that they should delete the project and create a new Win32 Application project (note that the word "Console" doesn't appear in this project type) with Visual C++ and add my source code files to it. The linker looks for a (w)WinMain entry-point function, which I do supply in my code, and the project will build properly.
To reduce the amount of e-mail I get on this issue, I added a pragma to CmnHdr.h that forces the linker to look for the (w)WinMain entry-point function even if you create a Win32 Console Application project with Visual C++.
In Chapter 4, I go into great detail about what the Visual C++ project types are all about, how the linker chooses which entry-point function to look for, and how to override the linker's default behavior.
Figure A-1. The CmnHdr.h header file
|