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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏风口上的猪的文章

.NET面试题系列[13] - LINQ to Object

"C# 3.0所有特性的提出都是更好地为LINQ服务的" - Learning Hard

1202
来自专栏JAVA后端开发

给mybatis添加自动建表,自动加字段的功能

以前项目用惯了hibernate,jpa,它有个自动建表功能,只要在PO里加上配置就可以了,感觉很爽. 但现在用mybatis,发现没有该功能,每次都加个字段...

5203
来自专栏积累沉淀

干货--Hadoop自定义数据类型和自定义输入输出格式整合项目案例

正文开始前 ,先介绍几个概念 序列化 所谓序列化,是指将结构化对象转化为字节流,以便在网络上传输或写到磁盘进行永久存储。 反序列化 是指将字节流转回到结构化...

6516
来自专栏我叫刘半仙

原自己手写一个Mybatis框架(简化)

       继上一篇手写SpringMVC之后,我最近趁热打铁,研究了一下Mybatis。MyBatis框架的核心功能其实不难,无非就是动态代理和jdbc的操...

2K6
来自专栏别先生

Java枚举类使用和总结

1152
来自专栏IMWeb前端团队

Redux源码解析系列(四)-- combineReducers

本文作者:IMWeb 黄qiong 原文出处:IMWeb社区 未经同意,禁止转载 combindeReducer 字面意思就是用来合并reducer的...

2007
来自专栏流媒体

resources.arsc解析

示例apk 示例代码 binary view二进制文件查看工具: android 6.0系统源码(网上搜索下载,这里暂不提供资源)

1672
来自专栏Hongten

spring开发_spring+hibernate

http://www.cnblogs.com/hongten/gallery/image/112469.html

1103
来自专栏程序员的SOD蜜

左求值表达式,堆栈,调试陷阱与ORM查询语言的设计

1,表达式的求值顺序与堆栈结构 “表达式” 是程序语言一个很重要的术语,也是大家天天写的程序中很常见的东西,但是表达式的求值顺序一定是从左到右么? C/C++语...

3296
来自专栏函数式编程语言及工具

FunDA(15)- 示范:任务并行运算 - user task parallel execution

    FunDA的并行运算施用就是对用户自定义函数的并行运算。原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例。这些函数运行实例...

1929

扫码关注云+社区

领取腾讯云代金券