错误处理
错误处理是<<windows核心编程>>这本书的第一章,由些可见,错误处理在我们写软件时的重要性是多么重要,当一个函数出错时,应该提示使用此函数的人员,出错的原因,以便于使用者进行修改,windows的错误处理就很棒,当我们使用windows API或者MFC类时,我们可以通过GetLastError来获取具体的错误原因,然后可以利用VS的工具Error Lookup来查找错误的原因,或者使用FormatMessage来打印出错误信息,以便于使用者查找错误。下面就说说windows的错误处理。
首先,windows函数会对传入的参数进行有效性的检查,然后在去执行其函数的功能,这也给我们自己写函数时提供了模板,我们自己写函数时,也有必要进行参数有效性的检查这一步。那么参数是有效的,并且也执行了,最后就涉及到一个函数返回值的问题,windows API常用的返回值有VOID,BOOL,HANDLE,PVOID,LONG,DWORD.文章最后说明这几个返回值的用法。
用windows API或者MFC开发软件,对于错误处理我们用GetLastError的地方比较多,看一下MSDN上的说明:
GetLastError
The GetLastError function retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis. Multiple threads do not overwrite each other's last-error code.
GetLastError函数检索调用线程的最后错误代码值。最后错误的代码是基于它上一个线程而被保存。多线程不会覆盖彼此的最后错误代码。
那么GetLastError是怎么样保持线程内的最后错误代码值的呢?这里得提到一个线程本地存储的概念(TLS:Thread-Local Storage,在这里记住TLS可以将数据与执行的特定线程关联起来就好。关于TLS的说明,将在以后的篇幅中来写,这篇不在讨论),线程本地存储可以将错误码与执行线程关联想来,这样,我们在调用函数后,如果出现函数执行错误,我们就可以用GetLastError来将具体的错误码获取到。这样就可以根据错误码得到具体的错误信息(用VS的工具Error Lookup或者是FormatMessage)。这里有一个地方需要注意,如果func1(函数执行)错误,我们又执行了func2(),无论func2()是否执行成功,那么func1()的错误将有可能被覆盖掉,所以当一个函数执行错误,我们想得到错误码,我们必须在函数执行完调用GetLastError来获取函数的错误码。
func1();//! 执行错误
DWORD dwRet = GetLastError(); //! 获取错误码
func2();//! 执行
一个错误码对了解该函数为什么执行失败起到了说明性的作用,那么windows又是怎么样定义错误码的呢?windows定义了一个包括所有错误码的列表,并且为每个错误码分配了一个32位的号码。这些32位的错误码在winerror.h中定义,下面只是winerror.h中的一小部分
/************************************************************************
* *
* winerror.h -- error code definitions for the Win32 API functions *
* *
* Copyright (c) Microsoft Corp. All rights reserved. *
* *
************************************************************************/
#ifndef _WINERROR_
#define _WINERROR_
#if defined (_MSC_VER) && (_MSC_VER >= 1020) && !defined(__midl)
#pragma once
#endif
//
// Values are 32 bit values layed out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |Sev|C|R| Facility | Code |
// +---+-+-+-----------------------+-------------------------------+
//
// where
//
// Sev - is the severity code
//
// 00 - Success
// 01 - Informational
// 10 - Warning
// 11 - Error
//
// C - is the Customer code flag
//
// R - is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
//
// Define the facility codes
//
#define FACILITY_WINDOWSUPDATE 36
#define FACILITY_WINDOWS_CE 24
#define FACILITY_WINDOWS 8
#define FACILITY_URT 19
#define FACILITY_UMI 22
#define FACILITY_SXS 23
#define FACILITY_STORAGE 3
#define FACILITY_STATE_MANAGEMENT 34
#define FACILITY_SSPI 9
#define FACILITY_SCARD 16
#define FACILITY_SETUPAPI 15
#define FACILITY_SECURITY 9
#define FACILITY_RPC 1
#define FACILITY_WIN32 7
#define FACILITY_CONTROL 10
#define FACILITY_NULL 0
#define FACILITY_METADIRECTORY 35
#define FACILITY_MSMQ 14
#define FACILITY_MEDIASERVER 13
#define FACILITY_INTERNET 12
#define FACILITY_ITF 4
#define FACILITY_HTTP 25
#define FACILITY_DPLAY 21
#define FACILITY_DISPATCH 2
#define FACILITY_DIRECTORYSERVICE 37
#define FACILITY_CONFIGURATION 33
#define FACILITY_COMPLUS 17
#define FACILITY_CERT 11
#define FACILITY_BACKGROUNDCOPY 32
#define FACILITY_ACS 20
#define FACILITY_AAF 18
//
// Define the severity codes
//
//
// 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,MessageText,还有就是具体的错码码了像3L,我们一般使用前两者,后者不用,MessageId是我们可以与GetLastError来进行比对,判断是什么错误,MessageText是对错误的描述.
FormatMessage
上面提到的MessageText可以用FormatMessage函数,根据MessageId获取出来.
DWORD FormatMessage(
DWORD dwFlags,
LPCVOID lpSource,
DWORD dwMessageId,
DWORD dwLanguageId,
LPTSTR lpBuffer,
DWORD nSize,
va_list* Arguments
);
dwFlags:
# FORMAT_MESSAGE_ALLOCATE_BUFFER // 此函数会分配内存以包含描述字串。
# FORMAT_MESSAGE_FROM_SYSTEM, // 在系统的id映射表中寻找描述字串
# FORMAT_MESSAGE_FROM_HMODULE // 在其他资源模块中寻找描述字串
# FORMAT_MESSAGE_FROM_STRING // 消息ID是个字串,不是个DWORD
通常为:FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
lpSource:
# 指定了FORMAT_MESSAGE_FROM_HMODULE的话,此参数表示模块的HANDLE
# 指定了FORMAT_MESSAGE_FROM_STRING的话,此参数表示id字串
通常为:NULL
dwMessageId:
消息ID;如果指定FORMAT_MESSAGE_FROM_STRING,将被忽略。
dwLanguageId:
消息描述所用的语言
通常为:0表示自动选择
lpBuffer:
#如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,则为自己提供的缓冲区
#否则为系统LocalAlloc分配,需要被用户LocalFree
nSize:
#如果未指定FORMAT_MESSAGE_ALLOCATE_BUFFER,则为自己提供的缓冲区大小
#否则为系统LocalAlloc分配之最小缓冲区大小
Arguments:
通常不使用
由上面可以看出,如果是自定义的错误码和信息的话,dwflag需要使用 FORMAT_MESSAGE_FROM_HMODULE,lpSource 需要模块的句柄,那么这个模块是什么模块呢?是"Message Text Files"(关于Message Text Files 见http://msdn.microsoft.com/en-us/library/dd996906(VS.85).aspx或者在MSDN中查mc.exe),通过mc.exe 可以把.mc文件编译成.h,.rc,.bin文件。.h文件是对错误代码的说明,结构与winerror.h相当,.rc规定了.bin文件作为包含错误信息的资源,然后用rc.exe 编译.rc文件,使之生成.res文件,最后用link.exe 链接.res,使之成为.dll文件,这样,一个错误信息表就做好了,.dll里包含有错误的字符串。具体的步骤见MSDN中的mc.exe.
错误码定义
错误代码的域
位 31~30 29 28 27~16 15~0
内容 严重性 Microsoft /客户 保留 设备代码 异常代码
含义 0=成功 0 =Microsoft公司定义的代码 必须是0 由Microsoft定义 由Microsoft/客户定义
1=供参考 1=客户定义的代码
2=警告
3=错误
由上表可以看出,当我们想自己定义错误码时,可以修改第29位(为1是客户自定义错误)和0~15位的值。
Windows常用返回类型
VOID:从这个返回类型我们可知,这个函数是不返回任何信息或者说值,还有一点我们要注意,这个还说明,返回值为VOID的函数永远不会执行失败,总是执行成功。当我们自定义函数时,除非非常肯定函数永远不会执行失败,否则不要用这个返回类型。
BOOL:说到BOOL,大家会想到bool,这两种数据类型有什么区别?BOOL定义在windef.h中,是这样定义的typedef int BOOL;就是说BOOL数据类型其实就是一个int型,它占4个字节,那么它的值也就是int型值,有0和非0两种,bool数据类型是C++标准中引入的,它只占一个字节,它有两个值,分别为true和false,true为1,false为0.说完BOOL与bool的区别,我们来看,函数返回值为BOOL时,我们怎么判断,
由BOOL的定义我们可知,为0表示函数执行失败,为非0时表示函数执行成功,由此我们判断一个返回值为BOOL的函数时,最后判断0和非0,不要盲目的判断是否为FALSE,和TRUE,因为FALSE值为0,TRUE的值为1.
HANDLE: 如果返回NULL,说明函数运行失败,否则返回HANDLE,但有的函数执行失败返回的是INVALID_HANDLE_VALID,它的值是-1,这个需要看具体的函数说明。