Windows核心编程之核心总结(第一章 错误处理)(2018.5.26)

2021-04-08 03:25

阅读:378

标签:Windows核心编程之核心总结

前沿

学习Windows核心编程是步入Windows编程殿堂的必经之路,2018年寒假重温了计算机操作系统知识,前阵子又过学习Windows程序设计方面的基础,正所谓打铁要乘热,所以我又入了Windows核心编程的坑啦,哈哈~

学习目标

每一章的学习都要明确一个目标,就是你学完这一章之后你能做些什么?好的,我们一步步来学习第一章节错误处理。以下是这一章节的学习目标:
1.了解Windows函数的错误机制
2.了解GetLastError和SetLastError函数的使用
3.了解FormatMessage函数使用及参数说明
4.通过以上的学习,自行写出将错误代码转换为错误信息代码例子。
5.结合Windows核心编程给出的ErrorShow示例程序,分析第4点自己写的代码。

Windows函数的错误机制

Windows核心编程这本书的第一章是最简单的,虽然简单,但我们不能骄傲,因 为我们要征服这本书就要以虚心向学的态度学习和总结,再应用。
我们都知道调用Windows函数时,它会先验证我们传给它的参数,验证通过后再开始执行任务。如果传入的参数无效或者由于其他原因导致操作无法执行,则函数的返回值将指出函数因为某些原因失败了。例如:最经典的函数莫过于CreateFile函数,假如打开文件失败,那么函数的返回值会是INVALID_HANDLE_VALUE(-1)。

HANDLE hFile=CreateFile(TEXT("c:\\fish"),0,0,NULL,OPEN_EXISTING,0,NULL);

下面展示大多数Windows函数使用的返回值的数据类型:

> VOID:这个函数不可能失败!
> BOOL:FALSE失败;TRUE成功。
> HANDLE:失败返回NULL,否则返回非零句柄。如果有特殊说明,则可能为特殊值例如:INVALID_HANDLE_VALUE。
> PVOID:返回一个内存地址,失败为NULL
> LONG/DWORD:应该根据SDK说明来确定函数状况。

可以发现,虽然函数的返回值能够告诉我们函数的执行失败,但是却没有告诉我们函数为什么会调用失败。所以,Microsoft编辑了一个列表,其中列出了所有可能的错误代码,并为每个错误代码都分配了一个32位的编号,其实错误代码是一个DWORD(unsigned long)类型的值。然后每个错误代码都定义了一个错误信息,这个错误列表是保存在WinError.h头文件中。下面截个WinError.h头文件的头部分:

#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: ERROR_TOO_MANY_OPEN_FILES
//
// MessageText:
//
// The system cannot open the file.
//
#define ERROR_TOO_MANY_OPEN_FILES        4L

GetLastError和SetLastError函数

好了,到了这里我们对Windows函数的错误机制有了一定的了解,那么我们程序中就要想办法获取错误代码和设置错误代码。
GetLastError函数获取调用线程的上一次错误代码。
函数原型:DWORD WINAPI GetLastError(void);
返回值:返回该线程的上一次错误代码。错误代码是一个32位无符号长整型(DWORD).
备注:Windows函数失败之后,应该马上调用GetLastError,因为有可能下一次的函数调用影响这次的错误代码值。
SetLastError函数用于为调用线程设置最近的错误代码。
函数原型:void WINAPI SetLastError(In DWORD dwErrCode);
参数:DWORD类型的错误代码。
备注:利用GetLastError函数返回线程的上一个错误代码,而通过这SetLastError函数设置错误代码。

(1)对于上面GetLastError和SetLastError函数的使用方法后面我会举例。现在,介绍一个Visual Studio的Watch窗口,以前学习C语言和C++我很少使用这个功能,一般都是看局部变量窗口,然后调试进行下一过程来查看各变量的变化情况。现在举个例子来说明Visual Studio的Watch窗口的使用方法。图片中监视1窗口的左侧名称是我自己输入进去的,例如:count、hmodule、$err,hr。
技术分享图片

(2)我们可以看到我将断点放在定义count的时候,当我输入1000时就进入调试阶段,其实WinError.h头文件并没有1000这个代码值的定义,那么这个FormatMessage函数的调用就会报错。然后点击逐过程进入下一条语句。
技术分享图片

(3)可以发现count变量成功被初始化成0了。而hmodule由于是条件语句内部还未执行到那,所以显示未定义。这时候我再点击逐过程进入下一条语句,这时候FormatMessage函数已经执行完成了,看下$err.hr的值变为错误代码加上错误代码描述信息。可以总结$err是显示函数调用失败的错误代码,而hr是错误代码的描述信息,你也可以只写$err,那么就不是错误代码信息了,只有错误代码。技术分享图片

(4)假如我们写一个函数给其他人调用,这个函数有可能会出错,那么我们需要向调用者指出错误,那么我们就可以自己在函数中调用SetLastError函数,然后我们的函数返回FALSE或者NULL或者其他合适的值。注意,SetLastError参数是一个DWORD类型值。而错误代码由几个字段组成,下面贴图:
技术分享图片

FormatMessage函数

下面是我对 FormatMessage函数的各参数一部分总结:

FormatMessage函数就是将GetLastError函数得到的错误信息(这个错误信息是数字代号)转化成字符串信息的函数。

函数原型:

DWORD WINAPI FormatMessage(
In DWORD dwFlags,
_Inopt LPCVOID lpSource,
In DWORD dwMessageId,
In DWORD dwLanguageId,
Out LPTSTR lpBuffer,
In DWORD nSize,
_Inopt va_list *Arguments
);

参数1:标志位
FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100
如果设置了这个标志位,那么参数lpBuffer(经过初始化的指针变量)必须按(LPTSTR)&lpBuffer形式作为参数来进行函数调用,当函数执行成功后,函数会自动分配一块内存块,该内存块含有我们想要的字符串,然后这个指针会指向该内存块,那么我们就可以引用该字符串了。

FORMAT_MESSAGE_ARGUMENT_ARRAY 0x00002000
这个标志位代表参数Arguments只是一个数组,不是va_list这种类型的参数。

FORMAT_MESSAGE_FROM_HMODULE 0x00000800
这个标志说明,这个函数接收一个DLL模块,从DLL模块中查找字符串。

FORMAT_MESSAGE_FROM_STRING 0x00000400
从一个字符串中,查找消息字符串。

FORMAT_MESSAGE_FROM_SYSTEM 0x00001000
从系统中获取消息字符串,不是从某个指定的字符串或者DLL中获取消息字符串。

FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200
这个表示说明Argument是否有用,如果设置了这个标志,那么Argument就会被函数忽略,如果没有设置这个标志位,那么就必须在*Argument参数中提供这些占位符的值。

参数2:lpSource:
从哪里获取消息字符串?如果是从系统获取消息字符串的话,这个参数为NULL。
参数3:dwMessageId:
消息索引
参数4:dwLanguageId:
消息的语言种类。
参数5:lpBuffer:
接受消息的内存块指针。
参数6:nSize:
接受消息的内存块大小,以字节为大小。
参数7:*Arguments:
消息中有些变量的值。
返回值:如果函数调用成功,返回字符消息的字符数。否则返回0.

自己编写的代码例子(附上注释说明)

我这个例子使用MFC简单对话框,利用FormatMessage函数实现将错误代码转化为错误代码信息,并将错误代码信息黏贴在静态文本控件。下面是Ok按钮的点击事件处理,重要的还是这个内部代码的实现,对于MFC不作过多讲解,其实我对MFC也只是小白+1的存在,我主要学习Qt界面的开发。

void CMFCApplication1Dlg::OnBnClickedOk()
{
    // TODO:  在此添加控件通知处理程序代码
    TCHAR *str=NULL;//消息字符串缓冲区
    this->UpdateData(1);

    /*
    FormatMessage函数进行参数的设置后,作用是为这个函数传入错误代码,该函数就会返回对应系统或者DLL文件或者字符串中错误代码信息字符串。
    该函数的返回值是一个DWORD类型的值,如果函数调用成功则返回字符串的字符数目,若失败则返回0.
    所以这里定义了一个count存储FormatMessage函数的返回值。
    */
    DWORD count = 0;
    count=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, m_value, NULL, (LPTSTR)&str, 0, NULL);
    //函数调用成功
    if (count)
    {
        this->SetDlgItemTextW(IDC_STATIC1, (LPCTSTR)LocalLock(str));//在静态文本控件的文本设置为所获取的错误代码信息
        LocalFree(str);//如果FormatMessage函数是通过函数自动分配内存块来存储错误代码信息,那么就要调用LocalFree函数释放掉这个内存。
    }
    //函数调用失败,因为上方调用失败的原因可能是输入的错误代码在系统定义中没有,那么就查找网络错误代码信息,这里加载网络错误代码信息的DLL文件
    else
    {
        //加载网络错误代码DLL文件,名字要记住
        HMODULE hmodule = LoadLibrary(TEXT("netmsg.dll"));
        //加载成功
        if (hmodule)
        {
            //再次调用FormatMessage函数,但参数1的标志不同,第一个标志变为FORMAT_MESSAGE_FROM_HMODULE
            count = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                hmodule, m_value, NULL, (LPTSTR)&str, 0, NULL);
            //函数调用成功
            if (count)
            {
                //在静态文本控件的文本设置为所获取的错误代码信息
                this->SetDlgItemTextW(IDC_STATIC1, (LPCTSTR)LocalLock(str));
                LocalFree(str);//释放函数自动分配的内存块
            }
            FreeLibrary(hmodule);//释放动态链接库文件的内存块
        }
    }
    //如果上面通过两种不同源路径都获取不到错误代码对应的信息,那么就输出一行报错文字
    if (count == 0)
    {
        this->SetDlgItemTextW(IDC_STATIC1, (LPCTSTR)L"没有找到错误代码信息");
    }
}

ErrorShow示例程序

DWORD dwError = GetDlgItemInt(hwnd, IDC_ERRORCODE, NULL, FALSE);//获取输入框的值,即错误代码值

      HLOCAL hlocal = NULL;   // Buffer that gets the error message string
      //HLOCAL=HANDLE=void *,这三个都是一样的

      // Use the default system locale since we look for Windows messages.
      // Note: this MAKELANGID combination has 0 as value
      DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);//语言

      // Get the error code‘s textual description
      BOOL fOk = FormatMessage(
         FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
         FORMAT_MESSAGE_ALLOCATE_BUFFER, 
         NULL, dwError, systemLocale, 
         (PTSTR) &hlocal, 0, NULL);
                 //如果函数调用失败则采取另一种方法来获取,即动态链接库文件
      if (!fOk) {
         // Is it a network-related error?
         HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL, 
            DONT_RESOLVE_DLL_REFERENCES);

         if (hDll != NULL) {
            fOk = FormatMessage(
               FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
               FORMAT_MESSAGE_ALLOCATE_BUFFER,
               hDll, dwError, systemLocale,
               (PTSTR) &hlocal, 0, NULL);
            FreeLibrary(hDll);
         }
      }

      if (fOk && (hlocal != NULL)) {
         SetDlgItemText(hwnd, IDC_ERRORTEXT, (PCTSTR) LocalLock(hlocal));
         LocalFree(hlocal);
      } else {
         SetDlgItemText(hwnd, IDC_ERRORTEXT, 
            TEXT("No text found for this error number."));
      }

总结

经过以上的学习,Windows核心编程的第一章就讲完了,也就那么多。我们分析自己写的例子和ErrorShow示例程序之间的区别其实不是很大,但FormatMessage示例程序的逻辑更加严谨。还有,如果也有童鞋正在学习Windows核心编程,我们可以相互交流学习,本人不才但好学研究,还需要不断学习来提升自己。可以通过QQ来联系我,我们一起不断进步!
QQ:764238383

Windows核心编程之核心总结(第一章 错误处理)(2018.5.26)

标签:Windows核心编程之核心总结

原文地址:http://blog.51cto.com/12731497/2120685


评论


亲,登录后才可以留言!