The CmnHdr.h Header File

[Previous] [Next]

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.

Windows Version Build Option

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.

Unicode Build Option

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.

Windows Definitions and Warning Level 4

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.

The Pragma Message Helper Macro

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.

The chINRANGE and chDIMOF Macros

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.

The chBEGINTHREADEX Macro

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))) 

DebugBreak Improvement for x86 Platforms

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.

Creating Software Exception Codes

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

The chMB macro simply displays a message box. The caption is the full pathname of the executable file for the calling process.

The chASSERT and chVERIFY Macros

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.

The chHANDLE_DLGMSG Macro

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.

The chSETDLGICONS Macro

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.

The OS Version Check Inline Functions

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.

Making Sure the Host System Supports Unicode

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.

Forcing the Linker to Look for a (w)WinMain Entry-Point Function

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

 

CmnHdr.h

/****************************************************************************** Module: CmnHdr.h Notices: Copyright (c) 2000 Jeffrey Richter Purpose: Common header file containing handy macros and definitions used throughout all the applications in the book. See Appendix A. ******************************************************************************/ #pragma once // Include this header file once per compilation unit //////////////////////// Windows Version Build Option ///////////////////////// #define _WIN32_WINNT 0x0500 //#define WINVER 0x0500 //////////////////////////// Unicode Build Option ///////////////////////////// // If we are not compiling for an x86 CPU, we always compile using Unicode. #ifndef _M_IX86 #define UNICODE #endif // To compile using Unicode on the x86 CPU, uncomment the line below. //#define UNICODE // When using Unicode Windows functions, use Unicode C-Runtime functions too. #ifdef UNICODE #define _UNICODE #endif ///////////////////////// Include Windows Definitions ///////////////////////// #pragma warning(push, 3) #include <Windows.h> #pragma warning(pop) #pragma warning(push, 4) ///////////// Verify that the proper header files are being used ////////////// #ifndef WT_EXECUTEINPERSISTENTTHREAD #pragma message("You are not using the latest Platform SDK header/library ") #pragma message("files. This may prevent the project from building correctly.") #endif ////////////// Allow code to compile cleanly at warning level 4 /////////////// /* nonstandard extension 'single line comment' was used */ #pragma warning(disable:4001) // unreferenced formal parameter #pragma warning(disable:4100) // Note: Creating precompiled header #pragma warning(disable:4699) // function not inlined #pragma warning(disable:4710) // unreferenced inline function has been removed #pragma warning(disable:4514) // assignment operator could not be generated #pragma warning(disable:4512) ///////////////////////// Pragma message helper macro ///////////////////////// /* When the compiler sees a line like this: #pragma chMSG(Fix this later) it outputs a line like this: c:\CD\CmnHdr.h(82):Fix this later You can easily jump directly to this line and examine the surrounding code. */ #define chSTR2(x) #x #define chSTR(x) chSTR2(x) #define chMSG(desc) message(_ _FILE_ _ "(" chSTR(_ _LINE_ _) "):" #desc) ////////////////////////////// chINRANGE Macro //////////////////////////////// // This macro returns TRUE if a number is between two others. #define chINRANGE(low, Num, High) (((low) <= (Num)) && ((Num) <= (High))) //////////////////////////////// chDIMOF Macro //////////////////////////////// // This macro evaluates to the number of elements in an array. #define chDIMOF(Array) (sizeof(Array) / sizeof(Array[0])) ///////////////////////////// chBEGINTHREADEX Macro /////////////////////////// // This macro function calls the C runtime's _beginthreadex function. // The C runtime library doesn't want to have any reliance on Windows' data // types such as HANDLE. This means that a Windows programmer needs to cast // values when using _beginthreadex. Since this is terribly inconvenient, // I created this macro to perform the casting. 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))) ////////////////// DebugBreak Improvement for x86 platforms /////////////////// #ifdef _X86_ #define DebugBreak() _asm { int 3 } #endif /////////////////////////// Software Exception Macro ////////////////////////// // Useful macro for creating your own software exception codes #define MAKESOFTWAREEXCEPTION(Severity, Facility, Exception) \ ((DWORD) ( \ /* Severity code */ (Severity ) | \ /* MS(0) or Cust(1) */ (1 << 29) | \ /* Reserved(0) */ (0 << 28) | \ /* Facility code */ (Facility << 16) | \ /* Exception code */ (Exception << 0))) /////////////////////////// Quick MessageBox Macro //////////////////////////// inline void chMB(PCSTR s) { char szTMP[128]; GetModuleFileNameA(NULL, szTMP, chDIMOF(szTMP)); MessageBoxA(GetActiveWindow(), s, szTMP, MB_OK); } //////////////////////////// Assert/Verify Macros ///////////////////////////// inline void chFAIL(PSTR szMsg) { chMB(szMsg); DebugBreak(); } // Put up an assertion failure message box. inline void chASSERTFAIL(LPCSTR file, int line, PCSTR expr) { char sz[128]; wsprintfA(sz, "File %s, line %d : %s", file, line, expr); chFAIL(sz); } // Put up a message box if an assertion fails in a debug build. #ifdef _DEBUG #define chASSERT(x) if (!(x)) chASSERTFAIL(_ _FILE_ _, _ _LINE_ _, #x) #else #define chASSERT(x) #endif // Assert in debug builds, but don't remove the code in retail builds. #ifdef _DEBUG #define chVERIFY(x) chASSERT(x) #else #define chVERIFY(x) (x) #endif /////////////////////////// chHANDLE_DLGMSG Macro ///////////////////////////// // The normal HANDLE_MSG macro in WindowsX.h does not work properly for dialog // boxes because DlgProc return a BOOL instead of an LRESULT (like // WndProcs). This chHANDLE_DLGMSG macro corrects the problem: #define chHANDLE_DLGMSG(hwnd, message, fn) \ case (message): return (SetDlgMsgResult(hwnd, uMsg, \ HANDLE_##message((hwnd), (wParam), (lParam), (fn)))) //////////////////////// Dialog Box Icon Setting Macro //////////////////////// // Sets the dialog box icons inline void chSETDLGICONS(HWND hwnd, int idi) { SendMessage(hwnd, WM_SETICON, TRUE, (LPARAM) LoadIcon((HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(idi))); SendMessage(hwnd, WM_SETICON, FALSE, (LPARAM) LoadIcon((HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE), MAKEINTRESOURCE(idi))); } /////////////////////////// OS Version Check Macros /////////////////////////// inline void chWindows9xNotAllowed() { OSVERSIONINFO vi = { sizeof(vi) }; GetVersionEx(&vi); if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { chMB("This application requires features not present in Windows 9x."); ExitProcess(0); } } inline void chWindows2000Required() { OSVERSIONINFO vi = { sizeof(vi) }; GetVersionEx(&vi); if ((vi.dwPlatformId != VER_PLATFORM_WIN32_NT) && (vi.dwMajorVersion < 5)) { chMB("This application requires features present in Windows 2000."); ExitProcess(0); } } ///////////////////////////// UNICODE Check Macro ///////////////////////////// // Since Windows 98 does not support Unicode, issue an error and terminate // the process if this is a native Unicode build running on Windows 98. // This is accomplished by creating a global C++ object. Its constructor is // executed before WinMain. #ifdef UNICODE class CUnicodeSupported { public: CUnicodeSupported() { if (GetWindowsDirectoryW(NULL, 0) <= 0) { chMB("This application requires an OS that supports Unicode."); ExitProcess(0); } } }; // "static" stops the linker from complaining that multiple instances of the // object exist when a single project contains multiple source files. static CUnicodeSupported g_UnicodeSupported; #endif /////////////////////////// Force Windows subsystem /////////////////////////// #pragma comment(linker, "/subsystem:Windows") ///////////////////////////////// End of File /////////////////////////////////



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net