Chapter 1 -- Error Handling

[Previous] [Next]

Chapter 1

Before we jump in and start examining the many features that Microsoft Windows has to offer, you should understand how the various Windows functions perform their error handling.

When you call a Windows function, it validates the parameters that you pass to it and then attempts to perform its duty. If you pass an invalid parameter or if for some other reason the action cannot be performed, the function's return value indicates that the function failed in some way. Table 1-1 shows the return value data types that most Windows functions use.

Table 1-1. Common return types for Windows functions

Data Type Value to Indicate Failure
VOID This function cannot possibly fail. Very few Windows functions have a return type of VOID.
BOOL If the function fails, the return value is 0; otherwise, the return value is nonzero. It is always best to test this return value to see if it is 0 or nonzero. Avoid testing the return value to see if it is TRUE.
HANDLE If the function fails, the return value is usually NULL; otherwise, the HANDLE identifies an object that you can manipulate. Be careful with this one because some functions return a handle value of INVALID_HANDLE_ VALUE, which is defined as -1. The Platform SDK documentation for the function will clearly state whether the function returns NULL or INVALID_HANDLE_ VALUE to indicate failure.
PVOID If the function fails, the return value is NULL; otherwise, the PVOID identifies the memory address of a data block.
LONG/DWORD This is a tough one. Functions that return counts usually return a LONG or DWORD. If for some reason the function can't count the thing you want counted, the function usually returns 0 or -1 (depending on the function). If you are calling a function that returns a LONG/DWORD, please read the Platform SDK documentation carefully to ensure that you are properly checking for potential errors.

When a Windows function returns with an error code, it's frequently useful to understand why the function failed. Microsoft has compiled a list of all possible error codes and has assigned each error code a 32-bit number.

Internally, when a Windows function detects an error, it uses a mechanism called thread-local storage to associate the appropriate error-code number with the calling thread. (Thread-local storage is discussed in Chapter 21.) This allows threads to run independently of each other without affecting each other's error codes. When the function returns to you, its return value will indicate that an error has occurred. To see exactly which error this is, call the GetLastError function:

 DWORD GetLastError(); 

This function simply returns the thread's 32-bit error code.

Now that you have the 32-bit error code number, you need to translate that number into something more useful. The WinError.h header file contains the list of Microsoft-defined error codes. I'll reproduce some of it here so you can see what it looks like:

 // MessageId: ERROR_SUCCESS // // MessageText: // // The operation completed successfully. // #define ERROR_SUCCESS 0L #define NO_ERROR 0L // dderror #define SEC_E_OK ((HRESULT)0x00000000L) // // MessageId: ERROR_INVALID_FUNCTION // // MessageText: // // Incorrect function. // #define ERROR_INVALID_FUNCTION 1L // dderror // // MessageId: ERROR_FILE_NOT_FOUND // // MessageText: // // The system cannot find the file specified. // #define ERROR_FILE_NOT_FOUND 2L // // MessageId: ERROR_PATH_NOT_FOUND // // MessageText: // // The system cannot find the path specified. // #define ERROR_PATH_NOT_FOUND 3L // // MessageId: ERROR_TOO_MANY_OPEN_FILES // // MessageText: // // The system cannot open the file. // #define ERROR_TOO_MANY_OPEN_FILES 4L // // MessageId: ERROR_ACCESS_DENIED // // MessageText: // // Access is denied. // #define ERROR_ACCESS_DENIED 5L 

As you can see, each error has three representations: a message ID (a macro that you can use in your source code to compare against the return value of GetLastError), message text (an English text description of the error), and a number (which you should avoid using and instead use the message ID). Keep in mind that I selected only a very tiny portion of the WinError.h header file to show you; the complete file is more than 21,000 lines long!

When a Windows function fails, you should call GetLastError right away or the value is very likely to be overwritten if you call another Windows function.

NOTE
GetLastError returns the last error generated by the thread. If the thread calls a Windows function that succeeds, the last error code is not overwritten and will not indicate success. A few Windows functions violate this rule and do change the last error code; however, the Platform SDK documentation usually indicates that the function changes the last error code when the function succeeds.

WINDOWS 98
Many Windows 98 functions are actually implemented in 16-bit code that originated from Microsoft's 16-bit Windows 3.1 product. This older code did not report errors via a function like GetLastError, and Microsoft did not "fix" the 16-bit code in Windows 98 to support this error handling. What this means to us is that many Win32 functions in Windows 98 do not set the last error code when they fail. The function will return a value that indicates failure so that you can detect that the function did, in fact, fail. But you will not be able to determine the cause of the failure.

Some Windows functions can succeed for several reasons. For example, attempting to create a named event kernel object can succeed either because you actually create the object or because an event kernel object with the same name already exists. Your application might need to know the reason for success. To return this information to you, Microsoft chose to use the last error-code mechanism. So when certain functions succeed, you can determine additional information by calling GetLastError. For functions with this behavior, the Platform SDK documentation clearly states that GetLastError can be used this way. See the documentation for the CreateEvent function for an example.

While debugging, I find it extremely useful to monitor the thread's last error code. In Microsoft Visual Studio 6.0, Microsoft's debugger supports a useful feature—you can configure the Watch window to always show you the thread's last error code number and the English text description of the error. This is done by selecting a row in the Watch window and typing "@err,hr". Examine Figure 1-1. You'll see that I've called the CreateFile function. This function returned a HANDLE of INVALID_HANDLE_VALUE (-1), indicating that it failed to open the specified file. But the Watch window shows us that the last error code (the error code that would be returned by the GetLastError function if I called it) is 0x00000002. The Watch window further indicates that error code 2 is "The system cannot find the file specified." You'll notice that this is the same string mentioned in the WinError.h header file for error code number 2.

click to view at full size.

Figure 1-1. Using "@err,hr" in Visual Studio 6.0's Watch window to view the current thread's last error code

Visual Studio also ships with a small utility called Error Lookup. You can use Error Lookup to convert an error code number into its textual description.

If I detect an error in an application I've written, I might want to show the text description to the user. Windows offers a function that will convert an error code into its text description. This function is called FormatMessage and is shown below.

 DWORD FormatMessage( DWORD dwFlags, LPCVOID pSource, DWORD dwMessageId, DWORD dwLanguageId, PTSTR pszBuffer, DWORD nSize, va_list *Arguments); 

FormatMessage is actually quite rich in functionality and is the preferred way of constructing strings that are to be shown to the user. One reason for this function's usefulness is that it works easily with multiple languages. This function detects the user's preferred language (as set in the Regional Settings Control Panel applet) and returns the appropriate text. Of course, first you must translate the strings yourself and embed the translated message table resource inside your .exe or DLL module, but then the function will select the correct one. The ErrorShow sample application (shown later in this chapter) demonstrates how to call this function to convert a Microsoft-defined error code number into its text description.

Every now and then, someone asks me if Microsoft produces a master list indicating all the possible error codes that can be returned from every Windows function. The answer, unfortunately, is no. What's more, Microsoft will never produce this list—it's just too difficult to construct and maintain as new versions of the system are created.

The problem with assembling such a list is that you can call one Windows function, but internally that function might call another function, and so on. Any of these functions could fail, for lots of different reasons. Sometimes when a function fails the higher-level function can recover and still perform what you want it to. To create this master list, Microsoft would have to trace the path of every function and build the list of all possible error codes. This is difficult. And as new versions of the system were created, these function-execution paths would change.



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