c语言输出win服务错误信息,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。

5d67b53fb3ff486c0d9eab5fb9ae64ed.png

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

4e9cfa50cb93168fb2fe25b0ac0c4f73.png

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

c3f4178db018d9d2568869002012aa24.png

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

79daed1ac477a6f09f0143c1cde73c54.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值