Chapter 1: Error Handling

Skip Navigation

Windows Via C/C++
Chapter 1 - Error Handling
Windows Via C/C++
by燡effrey Richter燼nd燙hristophe Nasarre?/TD>
Microsoft Press ?2008
 
Previous Section
Progress: Progress:Progress: 3% Progress:
Next Section

Chapter 1: Error Handling

第一章:错误处理

 

Overview

概述

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.

在我们开始考究Microsoft Windows提供的许多特性之前,你应该了解各个Windows函数是如何进行错误处理的。

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.

当你调用一个Windows 函数,你需要传入使之有效的参数,然后才会被尝试执行它的任务。如果你传入一个无效输出或者假如一些其他原因导致任务无法被执行,函数返回值会指示函数失败的一些情况。表1-1展示了大部分Windows functions使用的返回值类型。

Table 1-1: Common Return Types for Windows Functions

Windows 函数的通用返回类型
Open table as spreadsheet

Data Type

数据类型

Value to Indicate Failure

值的失败表示

VOID

This function cannot possibly fail. Very few Windows functions have a return type of VOID.

函数不可能失败,非常少的Windows函数会是VOID返回类型。

BOOL

If the function fails, the return value is 0; otherwise, the return value is non-zero. Avoid testing the return value to see if it is TRUE: it is always best to test this return value to see if it is different from FALSE.

如果函数失败,返回值是0,否则,返回值非零。避免检测返回值是TRUE,而应该测试返回值不是FASLE。

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.

如果函数失败,返回值通常是NULL,否则,HANDLE标识一个你能操控的对象。有一点要非常注意,一些函数返回一个INVALID_HANDLE_VALUE的句柄值,它被定义成-1。这些函数会在Platform SDK文档中明确说明函数是否返回NULL或者INVALID_HANDLE_VALUE以标识错误。

PVOID

If the function fails, the return value is NULL; otherwise, PVOID identifies the memory address of a data block.

如果函数失败,返回值是NULL,否则,PVOID指向一个数据块的内存地址。

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.

这个比较蛋疼,返回一个计数的函数通常返回一个LONG或者DWORD。如果一些原因函数不能记载你想要的计数,函数通常返回0或-1(取决于具体的函数)。如果你正在调用一个LONG/DWORD的函数,请仔细阅读Platform SDK文档以确保你能检测出潜在的错误。

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.

当某个Windows函数以一个错误码返回,这对了解函数失败的原因总是很有用。Microsoft已经编辑了一份所有可能的错误码列表并且为每一个代码分配了一个32-bit的数值。

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, "Thread-Local Storage.") This mechanism allows threads to run independently of each other without affecting each other's error codes. When the function returns to you, its return value indicates that an error has occurred. To see exactly which error this is, call the GetLastError function:在内部,当一个Windows函数发生一个错误,它使用一个调用本地线程存储机制

DWORD GetLastError();

This function simply returns the thread's 32-bit error code set by the last function call.

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 that 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 39,000 lines long!

When a Windows function fails, you should call GetLastError right away because the value is very likely to be overwritten if you call another Windows function. Notice that a Windows function that succeeds might overwrite this value with ERROR_SUCCESS.

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 where ERROR_ALREADY_EXISTS is returned when a named event already exists.

While debugging, I find it extremely useful to monitor the thread's last error code. In Microsoft Visual Studio, 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 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. Thanks to the ,hr qualifier, 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.

Image from book
Figure 1-1: Using $err,hr in Visual Studio'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.

Image from book

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 converts an error code into its text description. This function is called FormatMessage:

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 takes a language identifier as a parameter 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.

Previous Section
Progress: Progress:Progress: 3% Progress:
Next Section
posted on 2010-10-09 19:44  cifry  阅读(76)  评论(0)    收藏  举报