windows核心编程第1章

错误处理



1、内容介绍

《Windows核心编程》开篇并没有直接告诉我们 Microsoft Windows提供的诸多特性,而是先告诉我们Windows API如何处理进行错误处理。
通常我们调用Windows API都会遇到调用失败的情况,但是如果知道Windows API返回的错误码以及对应的意思,将有助于我们理解函数为什么会调用失败。要查看具体是什么错误,需要调用GetLastError函数。

(1)GetLastError函数

函数原型
WINBASEAPI
_Check_return_ _Post_equals_last_error_
DWORD
WINAPI
GetLastError(
    VOID
    );

返回:上一个函数调用设置的线程的32位错误码。

有了错误码之后,我们需要查看他对应的具体错误信息。WinError.h 头文件中包含了Microsoft定义的错误码列表。
错误码举例:

//
// MessageId: ERROR_INVALID_FUNCTION
//
// MessageText:
//
// Incorrect function.
//
#define ERROR_INVALID_FUNCTION           1L    // dderror

每个错误都包含3种表示:

  1. 一个消息ID(一个可在源代码中使用的宏,用于与GetLastError的返回值进行比较)。如上面的 ERROR_INVALID_FUNCTION
  2. 消息文本(描述错误的英文文本)。如上面的:Incorrect function
  3. 一个编号(应该避免使用此编号,尽量使用消息ID)。如上面的:1L

注意: Windows API调用失败之后,应该马上调用GetLastError,因为假如又调用了另一个Windows API,此值很可能被改写。

补充: 对于特定API调用成功时,可以调用GetLastError来确定额外的信息。对于具有这种行为的API,Platform SDK文档会清楚指明能以这种方式调用GetLastError。

(2)监视窗口

在代码调试过程中我们可以通过“监视”窗口,让它显示线程的上一个错误代码和无措的文本描述。
具体做法:在监视窗口中选中名称一行,然后输入:$err,hr
这相当于调用GetLastError
在这里插入图片描述

(3)错误查找窗口

想要知道错误对应的错误信息也可以通过错误查找窗口查询。
在这里插入图片描述

在这里插入图片描述

(4)FormatMessage函数

GetLastError可以获得对应的错误码,但是想要生成向用户显示错误的文本描述,而不是显示一个错误编码,则需要调用FormatMessage。

函数原型
WINBASEAPI
_Success_(return != 0)
DWORD
WINAPI
FormatMessageA(
    _In_     DWORD dwFlags,
    _In_opt_ LPCVOID lpSource,
    _In_     DWORD dwMessageId,
    _In_     DWORD dwLanguageId,
    _When_((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) != 0, _At_((LPSTR*)lpBuffer, _Outptr_result_z_))
    _When_((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) == 0, _Out_writes_z_(nSize))
             LPSTR lpBuffer,
    _In_     DWORD nSize,
    _In_opt_ va_list *Arguments
    );
//UNICODE字符集
WINBASEAPI
_Success_(return != 0)
DWORD
WINAPI
FormatMessageW(
    _In_     DWORD dwFlags,
    _In_opt_ LPCVOID lpSource,
    _In_     DWORD dwMessageId,
    _In_     DWORD dwLanguageId,
    _When_((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) != 0, _At_((LPWSTR*)lpBuffer, _Outptr_result_z_))
    _When_((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) == 0, _Out_writes_z_(nSize))
             LPWSTR lpBuffer,
    _In_     DWORD nSize,
    _In_opt_ va_list *Arguments
    );

参数说明:
dwFlags: 标志位,决定如何说明lpSource参数,dwFlags的低位指定如何处理换行功能在输出缓冲区,也决定最大宽度的格式化输出行。
可选参数:(标红为常用参数)

标志标志说明
FORMAT_MESSAGE_ALLOCATE_BUFFER函数会分配一个足够大的缓冲区保存格式化消息,并且通过lpBuffer指向该地址。当不再使用lpBuffer数据时,需调用LocalFree释放内存。
FORMAT_MESSAGE_ARGUMENT_ARRAYArguments参数不是指向va_list结构体,是一个指向保存参数的数组指针。
FORMAT_MESSAGE_FROM_HMODULE指定lpSource参数是要去搜索的一个包含消息表的模块句柄。如果 lpSource 是NULL,会搜索当前进程的主模块,这个标志不能与FORMAT_MESSAGE_FROM_STRING 同时使用。
FORMAT_MESSAGE_FROM_STRINGlpSource参数是一个指向以NULL结尾的字符串,字符串包含一个消息定义,这个消息定义可以包含插入序列。此标志不能与FORMAT_MESSAGE_FROM_HMODULE 、FORMAT_MESSAGE_FROM_SYSTEM同时使用
FORMAT_MESSAGE_FROM_SYSTEM函数会从系统信息列表中搜索所请求的信息。如果使用FORMAT_MESSAGE_FROM_HMODULE,函数会先在lpSource指定的模块中搜索请求的消息,如果搜索不到再去搜索系统消息表资源。此标志不能与FORMAT_MESSAGE_FROM_STRING同时使用。
FORMAT_MESSAGE_IGNORE_INSERTS指定消息定义中的插入序列将被忽略,并将其直接传递给输出缓冲区。 此标志对于获取稍后格式化的消息很有用。 如果设置了此标志,则忽略Arguments参数。

lpSource: 根据dwFlags标志而定。
dwMessageId:请求的消息的标识符。当dwFlags标志为FORMAT_MESSAGE_FROM_STRING时会被忽略。
dwLanguageId:请求的消息的语言标识符。
LPTSTR lpBuffer:接收错误信息描述的缓冲区指针。
nSize:如果FORMAT_MESSAGE_ALLOCATE_BUFFER标志没有被指定,这个参数必须指定为输出缓冲区的大小,如果指定值为0,这个参数指定为分配给输出缓冲区的最小数。
Arguments:保存格式化信息中的插入值的一个数组。

补充: FormatMessage轻松支持多种语言,通过获取一个语言标识符作为参数,并返回那种语言的文本


2、ErrorShow示例程序

在网上找的随书源码使用的是MFC,本用例根据大体流程使用Qt写了一个Demo。

在这里插入图片描述
源码:

注意: 部分ui变量名需要自己重新定义

//头文件

#ifndef ERRORSHOW_H_
#define ERRORSHOW_H_

#include <QtWidgets/QWidget>
#include "ui_errorShow.h"

namespace Chapter1
{
	class CErrorShow : public QWidget
	{
		Q_OBJECT

	public:
		CErrorShow(QWidget *parent = Q_NULLPTR);
		~CErrorShow();

	protected slots:
		//查询错误码
		void OnBtnLookUpClicked();

	private:
		Ui::CErrorShow ui;
	};
}

#endif //!ERRORSHOW_H_

//cpp文件

#include "errorShow.h"
#include <windows.h>

namespace Chapter1
{
	CErrorShow::CErrorShow(QWidget *parent)
		: QWidget(parent)
	{
		ui.setupUi(this);

		connect(ui.btnLookup, SIGNAL(clicked()), this, SLOT(OnBtnLookUpClicked()));
	}

	CErrorShow::~CErrorShow()
	{
	}

	void CErrorShow::OnBtnLookUpClicked()
	{
		QString strCode = ui.ldCode->text();
		if (strCode.isEmpty())
		{
			return;
		}

		//错误码
		DWORD ulCode = strCode.toULong();
		//错误信息
		HLOCAL hLocale = NULL;

		//MAKELANGID-Windows宏,用来从一个主语言标识符和从语言标识符创建一个语言标识符。
		//LANG_NEUTRAL和SUBLANG_NEUTRAL联合到一起将生成一个0值,即操作系统的默认语言
		DWORD ulSystemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

		//获取错误码对应的错误信息
		//FORMAT_MESSAGE_FROM_SYSTEM:获取与一个系统定义的错误代码对应的字符串
		//FORMAT_MESSAGE_IGNORE_INSERTS:允许获得含有%占位符的消息
		//FORMAT_MESSAGE_ALLOCATE_BUFFER:要求函数分配一块足以容纳错误文本描述的内存
		BOOL bOk = FormatMessage(
			FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
			FORMAT_MESSAGE_ALLOCATE_BUFFER,
			NULL, ulCode, ulSystemLocale,
			(PTSTR)&hLocale, 0, NULL
		);

		if (!bOk)
		{
			//失败则尝试在NetMsg.dll模块中查找消息代码,看错误是否与网络有关
			HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL, DONT_RESOLVE_DLL_REFERENCES);
			if (hDll != NULL)
			{
				bOk = FormatMessage(
					FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
					FORMAT_MESSAGE_ALLOCATE_BUFFER,
					hDll, ulCode, ulSystemLocale,
					(PTSTR)&hLocale, 0, NULL
				);
				FreeLibrary(hDll);
			}
		}

		if (bOk && (hLocale != NULL))
		{
			//字符集转换一下
			PCTSTR wszErrorMsg = (PCTSTR)LocalLock(hLocale);
			std::string strMsg;
			DWORD dwMinSize = 0;
			LPSTR lpszStr = NULL;
			dwMinSize = WideCharToMultiByte(CP_OEMCP, NULL, wszErrorMsg, -1, NULL, 0, NULL, FALSE);
			if (0 == dwMinSize)
			{
				return;
			}
			lpszStr = new char[dwMinSize];
			WideCharToMultiByte(CP_OEMCP, NULL, wszErrorMsg, -1, lpszStr, dwMinSize, NULL, FALSE);
			strMsg = lpszStr;
			delete[] lpszStr;
			ui.textBrowser->setText(QString::fromLocal8Bit(strMsg.c_str()));
		}
		else
		{
			ui.textBrowser->setText(tr("No text found for this error code!"));
		}

		LocalUnlock(hLocale);
		LocalFree(hLocale);
	}
}


源码下载:免费下载,仅供参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值