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

前沿

学习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函数设置错误代码。

对于上面GetLastError和SetLastError函数的使用方法后面我会举例。现在,介绍一个Visual Studio的Watch窗口,以前学习C语言和C++我很少使用这个功能,一般都是看局部变量窗口,然后调试进行下一过程来查看各变量的变化情况。现在举个例子来说明Visual Studio的Watch窗口的使用方法。图片中监视1窗口的左侧名称是我自己输入进去的,例如:count、hmodule、$err,hr。
![](http://i2.51cto.com/images/blog/201805/26/611af6af51e6b1631d6c0afebcd93724.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
我们可以看到我将断点放在定义count的时候,当我输入1000时就进入调试阶段,其实WinError.h头文件并没有1000这个代码值的定义,那么这个FormatMessage函数的调用就会报错。然后点击逐过程进入下一条语句。
![](http://i2.51cto.com/images/blog/201805/26/ff6c87706f3cdc5ebd948ad6ea16bf36.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
可以发现count变量成功被初始化成0了。而hmodule由于是条件语句内部还未执行到那,所以显示未定义。这时候我再点击逐过程进入下一条语句,这时候FormatMessage函数已经执行完成了,看下$err.hr的值变为错误代码加上错误代码描述信息。可以总结$err是显示函数调用失败的错误代码,而hr是错误代码的描述信息,你也可以只写$err,那么就不是错误代码信息了,只有错误代码。
![](http://i2.51cto.com/images/blog/201805/26/4fdee3bf639c61f440345e7b6bc16e48.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
假如我们写一个函数给其他人调用,这个函数有可能会出错,那么我们需要向调用者指出错误,那么我们就可以自己在函数中调用SetLastError函数,然后我们的函数返回FALSE或者NULL或者其他合适的值。注意,SetLastError参数是一个DWORD类型值。而错误代码由几个字段组成,下面贴图:
![](http://i2.51cto.com/images/blog/201805/26/f7489546292b8f3d8105b370bd628eed.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
# FormatMessage函数
下面是我对 FormatMessage函数的各参数一部分总结:
> FormatMessage函数就是将GetLastError函数得到的错误信息(这个错误信息是数字代号)转化成字符串信息的函数。

函数原型:

DWORD WINAPI FormatMessage(
  _In_     DWORD   dwFlags,
  _In_opt_ LPCVOID lpSource,
  _In_     DWORD   dwMessageId,
  _In_     DWORD   dwLanguageId,
  _Out_    LPTSTR  lpBuffer,
  _In_     DWORD   nSize,
  _In_opt_ 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

猜你喜欢

转载自blog.51cto.com/12731497/2120680