• 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。

  • 相关阅读:
    那些年我们写过的T-SQL(下篇)(转)
    好的架构是进化来的,不是设计来的
    dhcpsrv:windows系统的优秀开源免费dhcp serve软件
    PXE(preboot execution environment):【网络】预启动执行环节:安装 debian 9系列:成功
    PXE(preboot execution environment):【网络】预启动执行环节:安装 ubuntu、rehat系列:成功
    问题:UltraISO:这个软件有问题,它制作的iso文件会造成无法正确识别。用PowerISO吧
    自windows8以后,所有版本(专业版、企业版、旗舰版)都支持从 vhd 启动
    SpringCloud Feign
    SpringCloud Netflix Ribbon(负载均衡)
    SpringCloud Netflix Eureka(服务注册/发现)
  • 原文地址:https://www.cnblogs.com/lanuage/p/7725737.html
Copyright © 2020-2023  润新知