错误处理
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种表示:
- 一个消息ID(一个可在源代码中使用的宏,用于与GetLastError的返回值进行比较)。如上面的 ERROR_INVALID_FUNCTION
- 消息文本(描述错误的英文文本)。如上面的:Incorrect function
- 一个编号(应该避免使用此编号,尽量使用消息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_ARRAY | Arguments参数不是指向va_list结构体,是一个指向保存参数的数组指针。 |
FORMAT_MESSAGE_FROM_HMODULE | 指定lpSource参数是要去搜索的一个包含消息表的模块句柄。如果 lpSource 是NULL,会搜索当前进程的主模块,这个标志不能与FORMAT_MESSAGE_FROM_STRING 同时使用。 |
FORMAT_MESSAGE_FROM_STRING | lpSource参数是一个指向以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);
}
}
源码下载:免费下载,仅供参考