在开始介绍Microsoft Windows 的特性之前,必须首先了解Wi n d o w s的各个函数是如何进
行错误处理的。
当调用一个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
文档,以确保能正确检查潜在的错误
一个Wi n d o w s函数返回的错误代码对了解该函数为什么会运行失败常常很有用。M i c r o s o f t
公司编译了一个所有可能的错误代码的列表,并且为每个错误代码分配了一个3 2位的号码。
从系统内部来讲,当一个Wi n d o w s函数检测到一个错误时,它会使用一个称为线程本地存
储器(thread-local storage)的机制,将相应的错误代码号码与调用的线程关联起来(线程本地存储
器将在第2 1章中介绍)。这将使线程能够互相独立地运行,而不会影响各自的错误代码。当函
数返回时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,请调用
G e t L a s t E r r o r函数:
该函数只返回线程的3 2位错误代码。
当你拥有3 2位错误代码的号码时,必须将该号码转换成更有用的某种对象。Wi n E r r o r. h头
文件包含了M i c r o s o f t公司定义的错误代码的列表。下面显示了该列表的某些内容,使你能够看
到它的大概样子:
第一部分程序员必读
如你所见,每个错误都有3种表示法:一个消息I D(这是你可以在源代码中使用的一个宏,
以便与G e t L a s t E r r o r的返回值进行比较),消息文本(对错误的英文描述)和一个号码(应该避免使用这个号码,可使用消息I D)。请记住,这里只显示了Wi n E r r o r. h头文件中的很少一部分内
容,整个文件的长度超过2 1 0 0 0行。
当Wi n d o w s函数运行失败时,应该立即调用G e t L a s t E r r o r函数。如果调用另一个Wi n d o w s函数,它的值很可能被改写。
注意G e t L a s t E r r o r能返回线程产生的最后一个错误。如果该线程调用的Wi n d o w s函数
运行成功,那么最后一个错误代码就不被改写,并且不指明运行成功。有少数
Wi n d o w s函数并不遵循这一规则,它会更改最后的错误代码;但是Platform SDK文档
通常指明,当函数运行成功时,该函数会更改最后的错误代码。
Wi n d o w s 9 8 许多Windows 98的函数实际上是用M i c r o s o f t公司的1 6位Windows 3.1产品产生的1 6位代码来实现的。这种比较老的代码并不通过G e t L a s t E r r o r之类的函数来
报告错误,而且M i c r o s o f t公司并没有在Windows 98中修改1 6位代码,以支持这种错误
处理方式。对于我们来说,这意味着Windows 98中的许多Wi n 3 2函数在运行失败时不
能设置最后的错误代码。该函数将返回一个值,指明运行失败,这样你就能够发现该
函数确实已经运行失败,但是你无法确定运行失败的原因。
有些Wi n d o w s函数之所以能够成功运行,其中有许多原因。例如,创建指明的事件内核对
象之所以能够取得成功,是因为你实际上创建了该对象,或者因为已经存在带有相同名字的事
件内核对象。你应搞清楚成功的原因。为了将该信息返回, M i c r o s o f t公司选择使用最后错误代
码机制。这样,当某些函数运行成功时,就能够通过调用G e t L a d t E r r o r函数来确定其他的一些
信息。对于具有这种行为特性的函数来说, Platform SDK文档清楚地说明了G e t L a s t E r r o r函数
可以这样使用。请参见该文档,找出C r e a t e E v e n t函数的例子。
进行调试的时候,监控线程的最后错误代码是非常有用的。在Microsoft Visual studio 6.0中,
M i c r o s o f t的调试程序支持一个非常有用的特性,即可以配置Wa t c h窗口,以便始终都能显示线
程的最后错误代码的号码和该错误的英文描述。通过选定Wa t c h窗口中的一行,并键入
“@ e r r, h r”,就能够做到这一点。观察图1 - 1,你会看到已经调用了C r e a t e F i l e函数。该函数返回
I N VA L I D _ H A N D L E _ VA L U E(- 1)的H A N D L E,表示它未能打开指定的文件。但是Wa t c h窗口
向我们显示最后错误代码(即如果调用G e t L a s t E r r o r函数,该函数返回的错误代码)是
0 x 0 0 0 0 0 0 0 2。该Wa t c h窗口又进一步指明错误代码2是指“系统不能找到指定的文件。”你会发
现它与Wi n E r r o r. h头文件中的错误代码2所指的字符串是相同的。
图1-1 在Visual Studio 6.0的Wa t c h窗口中键入
“@ e r r, h r”,就可以查看当前线程的最后错误代码
Visual studio还配有一个小的实用程序,
称为Error Lookup。可以使用Error Lookup
将错误代码的号码转换成相应文本描述(见
图1 - 2)。
如果在编写的应用程序中发现一个错误,
可能想要向用户显示该错误的文本描述。
Wi n d o w s提供了一个函数,可以将错误代码
转换成它的文本描述。该函数称为F o r m a t -
M e s s a g e,显示如下:
F o r m a t M e s s a g e函数的功能实际上是非常丰富的,在创建向用户显示的字符串信息时,它
是首选函数。该函数之所以有这样大的作用,原因之一是它很容易用多种语言进行操作。该函
数能够检测出用户首选的语言(在Regional Settings Control Panel小应用程序中设定),并返回
相应的文本。当然,首先必须自己转换字符串,然后将已转换的消息表资源嵌入你的. e x e文件
或D L L模块中,然后该函数会选定正确的嵌入对象。E r r o r S h o w示例应用程序(本章后面将加
以介绍)展示了如何调用该函数,以便将M i c r o s o f t公司定义的错误代码转换成它的文本描述。
有些人常常问我,M i c r o s o f t公司是否建立了一个主控列表,以显示每个Wi n d o w s函数可能
返回的所有错误代码。可惜,回答是没有这样的列表,而且M i c r o s o f t公司将永远不会建立这样
的一个列表。因为在创建系统的新版本时,建立和维护该列表实在太困难了。
建立这样一个列表存在的问题是,你可以调用一个Wi n d o w s函数,但是该函数能够在内部
调用另一个函数,而这另一个函数又可以调用另一个函数,如此类推。由于各种不同的原因,
这些函数中的任何一个函数都可能运行失败。有时,当一个函数运行失败时,较高级的函数对
它进行恢复,并且仍然可以执行你想执行的操作。为了创建该主控列表, M i c r o s o f t公司必须跟
踪每个函数的运行路径,并建立所有可能的错误代码的列表。这项工作很困难。而且,当创建
系统的新版本时,这些函数的运行路径还会改变。
1.1 定义自己的错误代码
前面已经说明Wi n d o w s函数是如何向函数的调用者指明发生的错误,你也能够将该机制用
于自己的函数。比如说,你编写了一个希望其他人调用的函数,你的函数可能因为这样或那样
的原因而运行失败,你必须向函数的调用者说明它已经运行失败。
若要指明函数运行失败,只需要设定线程的最后的错误代码,然后让你的函数返回FA L S E、
I N VA L I D _ H A N D L E _ VA L U E、N U L L或者返回任何合适的信息。若要设定线程的最后错误代
码,只需调用下面的代码:
请将你认为合适的任何3 2位号码传递给该函数。尝试使用Wi n E r r o r. h中已经存在的代码