zoukankan      html  css  js  c++  java
  • 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
    ", i, BackTrace[i]);
        }
        return 0;

    这段代码非常简短,函数只需要四个参数,第一个参数是表示从当前栈顶开始的第几个栈开始便利,第二个参数是共便利多少个栈信息,第三个参数是一个缓冲区,用来存储得到的栈信息,具体就是栈的地址。第四个参数是一个哈希数组,由函数本身返回,如果不需要这个可以设置为NULL。

  • 相关阅读:
    LeetCode 1275. 找出井字棋的获胜者 Find Winner on a Tic Tac Toe Game
    LeetCode 307. 区域和检索
    LeetCode 1271 十六进制魔术数字 Hexspeak
    秋实大哥与花 线段树模板
    AcWing 835. Trie字符串统计
    Leetcode 216. 组合总和 III
    Mybatis 示例之 复杂(complex)属性(property)
    Mybatis 示例之 复杂(complex)属性(property)
    Mybatis 高级结果映射 ResultMap Association Collection
    Mybatis 高级结果映射 ResultMap Association Collection
  • 原文地址:https://www.cnblogs.com/lanuage/p/7725737.html
Copyright © 2011-2022 走看看