windows错误处理
在调用windows API时函数会首先对我们传入的参数进行校验,然后执行,如果出现什么情况导致函数执行出错,有的函数可以通过返回值来判断函数是否出错,比如对于返回句柄的函数如果返回NULL 或者INVALID_HANDLE_VALUE,则函数出错,对于返回指针的函数来说如果返回NULL则函数出错,但是对于有的函数从返回值来看根本不知道是否成功,或者为什么失败,对此windows提供了一大堆的错误码,用于标识API函数是否出错以及出错原因。
在windows中为每个线程准备了一个存储区,专门用来存储当前API执行的错误码,想要获取这个错误码可以通过函数GetLastError。在这需要注意的是当前API执行返回的错误码会覆盖之前API返回的错误码,所以在调用API结束后需要立马调用GetLastError来获取该函数返回的错误码。但是windows中的错误码实在太多,有的时候错误码并不直观,windows为每个错误码都关联了一个错误信息的文本,想要通过错误码获取对应的文本信息,可以通过函数FormatMessage来获取。
下面是一个具体的例子:
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#define GRS_OUTPUT(s) WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), s, _tcsclen(s), NULL, NULL)
int _tmain(int argc, TCHAR *argv[])
{
if (INVALID_HANDLE_VALUE == CreateFile(_T("C:\\Test.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
{
LPTSTR lpMsg = NULL;
DWORD dwLastError = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, GetUserDefaultLangID(), (LPTSTR)&lpMsg, 0, NULL);
if (NULL != lpMsg)
{
TCHAR szErrorInfo[1024] = {0};
StringCchPrintf(szErrorInfo, sizeof(szErrorInfo), _T("打开文件失败,失败原因为:%s"), lpMsg);
GRS_OUTPUT(szErrorInfo);
HeapFree(GetProcessHeap(), 0, lpMsg);
}
}
return 0;
}
在这段代码中我们没有使用C标准库中的printf,而是使用了windows自带的控制台函数WriteConsole,为了简单,我们定义了一个宏,用来输出字符串。函数WriteConsole的原型如下:
BOOL WINAPI WriteConsole(
__in HANDLE hConsoleOutput,
__in const VOID* lpBuffer,
__in DWORD nNumberOfCharsToWrite,
__out LPDWORD lpNumberOfCharsWritten,
LPVOID lpReserved
);
函数的第一个参数是控制台的句柄,可以通过函数GetStdHandle来获取,这个函数主要传入一个标志,表示需要获取哪个控制台的句柄,主要有:STD_INPUT_HANDLE(标准输入)、STD_OUTPUT_HANDLE
(标准输出)、STD_ERROR_HANDLE(标准错误)
第二个参数是字符串的指针,第三个参数是字符个数,第四个参数是实际写入字符个数,由函数返回,如果不关心可以给NULL,最后一个windows作为保留参数通常给NULL。
程序首先以打开已存在文件的方式打开一个文件,由于这个文件并不存在,所以函数出错,我们通过GetLastError获取错误码,然后通过FormatMessage来进行转化,该函数原型如下:
DWORD FormatMessage(
DWORD dwFlags, //标志
LPCVOID lpSource, //根据第一个参数的不同而有不同的解释
DWORD dwMessageId, //错误码
DWORD dwLanguageId, //语言ID
LPTSTR lpBuffer, //字符缓冲区,用来存放最终生成的格式字符串
DWORD nSize, //缓冲区大小
va_list* Arguments//作为不定参数类似于printf函数格式化字符串后面的参数
);
第一个参数是标志,在这我们传入FORMAT_MESSAGE_ALLOCATE_BUFFER,表示字符串缓冲区由该函数为我们分配,而不用自己分配,这个时候为了接受返回的字符缓冲区指针,需要使用二级指针。传入FORMAT_MESSAGE_IGNORE_INSERTS表示忽略插入的信息,也就是说不需要进行sprintf那样的格式化字符串的操作,传入FORMAT_MESSAGE_FROM_SYSTEM表示错误信息的字符串来自于系统定义的。然后进行简单的格式化之后输出错误字符串,最后需要释放内存,虽然FormatMessage函数帮我们分陪了缓冲,但是它不负责释放,需要我们自行释放。
另外我们也可以自行进行错误码的设置,利用函数SetLastError可以达到这个效果,以模拟API调用时返回错误码的操作。在windows上一般遵循这样的格式:
位 | 31~30 | 29 | 28 | 27~16 | 15~0 |
---|---|---|---|---|---|
用途 | 严重性 | 系统错误码 | 保留位 | 设备码 | 异常代码 |
含义 | 0 成功 1供参考 2警告 3错误 |
0系统定义 1自定义 |
总为0 | 系统设备码 | 具体错误码 |
除了获取错误信息之外,还可以获取调用堆栈的快照,可以用函数CaptureStackBackTrace获取,只是这个函数只能获取调用堆栈的线性地址,不能获取到具体的函数名称。
下面是它具体的一个例子:
const int nCount = 128;
PVOID BackTrace[nCount] = {NULL};
int iCnt = CaptureStackBackTrace(0, nCount, BackTrace, NULL);
for (int i = 0; i < iCnt; i++)
{
printf("调用堆栈索引%d, 函数地址:0x%08x\n", i, BackTrace[i]);
}
return 0;
这段代码非常简短,函数只需要四个参数,第一个参数是表示从当前栈顶开始的第几个栈开始便利,第二个参数是共便利多少个栈信息,第三个参数是一个缓冲区,用来存储得到的栈信息,具体就是栈的地址。第四个参数是一个哈希数组,由函数本身返回,如果不需要这个可以设置为NULL。