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

(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

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

时间: 2024-10-12 03:56:26

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

Windows Forms编程实战学习:第一章 初识Windows Forms

初识Windows Forms 1,用C#编程 using System.Windows.Forms; ? [assembly: System.Reflection.AssemblyVersion("1.0")] ? namespace MyNamespace { public class MyForm : Form { public MyForm() { this.Text = "Hello Form"; } [System.STAThread] public s

Windows核心编程第一章.错误处理

Windows核心编程第一章,错误处理. 一丶错误处理 1.核心编程学习总结 不管是做逆向,开始做开发.在Windows下.你都需要看一下核心编程这本书.这本书确实写得很好.所以自己在学习这本书的同时,也把自己所学的知识进行 总结,以及巩固. 2.常见的Windows函数返回类型总结 数据类型 作用 VOID 如果是Void表示函数不可能失败.极少数windows函数会返回void BOOL 表示这个函数会有失败情况.0失败.否则就是非0.但是一般都会使用TRUE FALSE来判断. HANDL

编程之美笔记--第一章游戏之乐--1.2中国象棋将帅问题

后来一版作者又将最后一句改为:”要求在代码中只能使用一个字节存储变量“. 我的解法: package android.zlb.java; /** * * @author zhanglibin * */ public class TestXiangqi { public static void main(String[] args) { for(int i = 11; i < 100; i++) { if(i / 10 % 3 == 1 && (i % 10 == 1 || i % 1

读《UNIX系统编程》关键字解释 第一章

第一次看这本书的时候好混乱啊,这次准备再看一遍,仔仔细细的看一遍.并且把自己感觉要记的关键字找出. 版本1.01 Songsong整理 第一章:UNIX基础知识 1.内核:.“内核”指的是一个提供硬件抽象层.磁盘及文件系统控制.多任务等功能的系统软件.一个内核不是一套完整的操作系统.一套基于Linux内核的完整操作系统叫作Linux操作系统,或是GNU/Linux. 硬件抽象层是位于操作系统 内核与硬件电路之间的接口层,其目的在于将硬件抽象化.它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬

Windows核心编程之核心总结(第三章 内核对象)(2018.6.2)

学习目标 第三章内核对象的概念较为抽象,理解起来着实不易,我不断上网找资料和看视频,才基本理解了内核对象的概念和特性,其实整本书给我的感觉就是完整代码太少了,没有多少实践的代码对内容的实现,而且书本给的源码例子,有太多我们不知道的知识,并且这些知识对本章主要内容来说是多余的,所以我们理解起来也非常困难.为了更好的学习这章,我补充了一些辅助性内容.这一章的学习目标:1.Windows会话和安全机制2.什么是内核对象?3.使用计数和安全描述符4.内核对象句柄表5.创建内核对象6.关闭内核对象7.跨进

UNIX环境高级编程学习笔记(第一章UNIX基础知识)

总所周知,UNIX环境高级编程是一本很经典的书,之前我粗略的看了一遍,感觉理解得不够深入. 听说写博客可以提高自己的水平,因此趁着这个机会我想把它重新看一遍,并把每一章的笔记写在博客里面. 我学习的时候使用的平台是Windows+VMware+debian,使用secureCRT来连接(可以实现多个终端连接). 因为第一章是本书大概的描述,所以第一章的我打算写得详细一点,而且书本的原话占的比例会比较多,重点的东西会用粗体显示出来. 1.1  引言 所有操作系统都为他们所运行的程序提供服务.典型的

编程的精义(第一章,因为博客园问题,邮箱作业上传博客园)

一个人立足于社会,都要有自己的想法和坚持.正如大道至简开篇中写的:在周爱民老师被别人说稿子太薄的时候,问他能不能加厚一点,或者写一些感悟的来源和案例,以及多写一些故事的背景时,他一直坚持自己的观点,不加厚——因为这是<大道至简>中“简”之涵义所在. 第一次读这本书的时候,我只是抱着试试看的心态来读的,但当我读完序言的时候,我的心态就已经发生了很大的变化,我有些后悔没有早点读这本书.整个文章引起了我极大的兴趣.第一章以著名的寓言故事<愚公移山>为开篇,十分有趣并且给人以印象深刻,最让

对一千万条数据进行排序---编程珠玑第二版 第一章

本书第一章提出了一个看似简单的问题,有最多1000万条不同的整型数据存在于硬盘的文件中,如何在1M内存的情况下对其进行尽可能快的排序. 每个数字用4byte,1M即可存储250 000个数据,显然,只要每次对250 000个数据排序,写入到文件中即可,重复40次. 那么如何选出每次遍历的二十五万条数据呢?有如下两个策略: 1.对一千万条数据遍历40次,第i次遍历时,判断数是否属于[i*250000,i*250000+249999),如果是,则读入内存,当第i次遍历完成时,内 存中有了二十五万条数

《高质量C\C++编程》阅读笔记 第一章

第一章 文件结构 分为两个文件 1.头文件:保存程序的声明. C/C++都是".h"为后缀 2.定义文件:保存程序的实现. C ".c"为后缀,C++ ".cpp"为后缀 1.1 版权和版本的说明 位置:头文件和定义文件的开头.(版本信息)(文件名称.标识符.摘要)(当前版本号.作者.完成日期)(版本历史信息). 1.2 头文件的结构 <xxxx.h> 三部分: 1.版本和版本声明. 2.预处理块. 3.函数和类结构声明. ifnde