第1章 对程序错误的处理
当调用一个Windows函数时,它首先要检验传递给它的的各个参数的有效性,然后再设法执行任务。如果传递了一个无效参数,或者由于某种原因无法执行这项操作,那么操作系统就会返回一个值,指明该函数在某种程度上运行失败了。
表1-1 Wi n d o w s 函数常用的返回值类型
数据类型 | 表示失败的值 |
V O I D | 该函数的运行不可能失败。Wi n d o w s 函数的返回值类型很少是V O I D |
B O O L | 如果函数运行失败,那么返回值是0 ,否则返回的是非0 值。最好对返回值进行测试,以确定它是0 还是非0 。不要测试返回值是否为T R U E |
H A N D L E | 如果函数运行失败,则返回值通常是N U L L ,否则返回值为H A N D L E ,用于标识你可以操作的一个对象。注意,有些函数会返回一个句柄值I N VALID_ HANDLE_VA L U E ,它被定义为- 1 。函数的Platform SDK 文档将会清楚地说明该函数运行失败时返回的是N U L L 还是I N VA L I D _ H A N D L E _ VA L I D |
P V O I D | 如果函数运行失败,则返回值是N U L L ,否则返回P V O I D ,以标识数据块的内存地址 |
L O N G / D W O R D | 这是个难以处理的值。返回数量的函数通常返回L O N G 或D W O R D 。如果由于某种原因,函数无法对想要进行计数的对象进行计数,那么该函数通常返回0 或- 1 (根据函数而定)。如果调用的函数返回了L O N G / D W O R D ,那么请认真阅读Platform SDK文档,以确保能正确检查潜在的错误 |
一个Windows 函数返回的错误代码对了解该函数为什么会运行失败常常很有用。Microsoft公司编译了一个所有可能的错误代码的列表,并且为每个错误代码分配了一个3 2 位的号码。
从系统内部来讲,当一个Windows 函数检测到一个错误时,它会使用一个称为线程本地存储器(thread-local storage )的机制,将相应的错误代码号码与调用的线程关联起来(线程本地存储器将在第2 1 章中介绍)。这将使线程能够互相独立地运行,而不会影响各自的错误代码。当函数返回时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,请调用G e t L a s t E r r o r 函数:
DWORD GetLastError();该函数只返回线程的3 2 位错误代码。
当你拥有3 2 位错误代码的号码时,必须将该号码转换成更有用的某种对象。WinError. h 头文件包含了Microsoft 公司定义的错误代码的列表。
每个错误都有3 种表示法:一个消息I D (这是你可以在源代码中使用的一个宏,以便与GetLastError 的返回值进行比较),消息文本(对错误的英文描述)和一个号码(应该避免使用这个号码,可使用消息I D )。请记住,这里只显示了WinError. h 头文件中的很少一部分内容,整个文件的长度超过21000 行。
当Windows 函数运行失败时,应该立即调用GetLastError 函数。如果调用另一个Windows 函数,它的值很可能被改写。
注意GetLastError 能返回线程产生的最后一个错误。如果该线程调用的Wi n d o w s 函数运行成功,那么最后一个错误代码就不被改写,并且不指明运行成功。有少数Wi n d o w s 函数并不遵循这一规则,它会更改最后的错误代码;但是Platform SDK 文档通常指明,当函数运行成功时,该函数会更改最后的错误代码。
有些Windows 函数之所以能够成功运行,其中有许多原因。例如,创建指明的事件内核对象之所以能够取得成功,是因为你实际上创建了该对象,或者因为已经存在带有相同名字的事件内核对象。你应搞清楚成功的原因。为了将该信息返回,Microsoft 公司选择使用最后错误代码机制。这样,当某些函数运行成功时,就能够通过调用GetLastError 函数来确定其他的一些信息。对于具有这种行为特性的函数来说,Platform SDK 文档清楚地说明了GetLastError 函数可以这样使用。请参见该文档,找出CreateEvent 函数的例子。
进行调试的时候,监控线程的最后错误代码是非常有用的。如果在编写的应用程序中发现一个错误,可能想要向用户显示该错误的文本描述。Wi n d o w s 提供了一个函数,可以将错误代码转换成它的文本描述。该函数称为FormatMessage,显示如下:
DWORD FormatMessage(
DWORD dwFlags, // source and processing options
LPCVOID lpSource, // pointer to message source
DWORD dwMessageId, // requested message identifier
DWORD dwLanguageId, // language identifier for requested message
LPTSTR lpBuffer, // pointer to message buffer
DWORD nSize, // maximum size of message buffer
va_list *Arguments // pointer to array of message inserts
);
FormatMessage 函数的功能实际上是非常丰富的,在创建向用户显示的字符串信息时,它是首选函数。该函数之所以有这样大的
作用,原因之一是它很容易用多种语言进行操作。该函数能够检测出用户首选的语言(在Regional Settings Control Panel 小应用程序中设定),并返回相应的文本。当然 ,首先必须自己转换字符串,然后将已转换的消息表资源嵌入你的. e x e 文件或D L L 模块中,然后该函数会选定正确的嵌入对象。ErrorShow 示例应用程序(本章后面将加以介绍)展示了如何调用该函数,以便将Microsoft 公司定义的错误代码转换成它的文本描述。
前面已经说明Windows 函数是如何向函数的调用者指明发生的错误,你也能够将该机制用于自己的函数。比如说,你编写了一个希望其他人调用的函数,你的函数可能因为这样或那样的原因而运行失败,你必须向函数的调用者说明它已经运行失败。
若要指明函数运行失败,只需要设定线程的最后的错误代码,然后让你的函数返回FALSE 、INVALID_HANDLE_VALUE 、NULL 或者返回任何合适的信息。若要设定线程的最后错误代码,只需调用下面的代码:
请将你认为合适的任何3 2 位号码传递给该函数。尝试使用WinError.h 中已经存在的代码,
VOID SetLastError(DWORD dwErrCode);
只要该代码能够正确地指明想要报告的错误即可。如果你认为WinError.h 中的任何代码都不能正确地反映该错误的性质,那么可以创建你自己的代码 。错误代码是个3 2 位的数字,划分成表1-2所示的各个域。
表1-2 错误代码的域
位 | 3 1 ~30 | 29 | 28 | 27~16 | 15~0 |
内容 | 严重性 | M i c r o s o f t/客户 | 保留 | 设备代码 | 异常代码 |
含义 | 0 =成功 | 0 =M i c r o s o f t公司定义的代码 | 必须是0 | 由M i c r o s o f t公司定义 | 由Microsoft/客户定义 |
1 =供参考 | 1 =客户定义的代码 | ||||
2 =警告 | |||||
3 =错误 |
这些域将在第2 4 章中详细讲述。现在,需要知道的重要域是第29 位。M i c r o s o f t 公司规定,他们建立的所有错误代码的这个信息位均使用0 。如果创建自己的错误代码,必须使29 位为1 。这样,就可以确保你的错误代码与Microsoft 公司目前或者将来定义的错误代码不会发生冲突。