错误处理

错误处理

错误处理是<<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,这个需要看具体的函数说明。

 

 

 

你可能感兴趣的:(错误处理)