几个知识点:
1.当Windows函数返回值为BOOL时,如果函数运行失败,则返回0值,否则返回非0值。最好对返回值进行测试,以确定它是0还是非0。不要测试返回值是否为TRUE。
2.当返回值为HANDLE时,通常都是失败就返回NULL,但有时函数会返回一个被定义为-1的INVALID_HANDLE_VALUE值。因此,使用函数的时候应该查阅平台SDK文档,以确定返回值是什么。
3.从系统内部来讲,当一个Windows函数检测到一个错误时,它会使用一个称为线程本地存储(thread_local_storage)的机制,将相应的错误代码号与调用的线程关联起来,这将使线程能够互相独立地运行,而不会影响各自的错误代码。当函数返回时,它的返回值就能指明一个错误已经发生。若要确定这个错误,需要调用(DWORD GetLastError())函数。
4.若要在调试器中查看线程的错误信息,选定"Watch"窗口中的一行,并键入"@err,hr",就可以了。
5.如果要向用户显示或者想得到GetLastError所返回错误码的文本显示,需要用到(DWORD FormatMessage(...))函数,本文的后面,会使用代码演示此函数的使用方法。
6.若要设定线程的最后错误代码,使用(VOID SetLastError(DWORD dwErrCode))函数,其中dwErrCode可以是WinError.h中已经存在的定义码,也可以是自定义的错误码。
全书的第一章,通常都是比较浅显易懂的内容,本书的第一章详述了Windows中的对程序错误的处理方式,示例源程序展示了一个类似于Visual Studio中的错误查询功能,作为自己本章的练习,我使用C#来实现界面,免去繁琐的界面编程,但是,我还不晓得怎么直接用C#实现FormatMessage这个函数,所以,我使用平台调用,写一个非托管的dll来实现这个功能,然后在我的C#程序里面调用它。
首先,需要写一个标准的Windows动态库(ErrorLookup.dll)以供C#调用,代码如下:
-----------------------------------------------------error.h-------------------------------------------------------------------
#ifndef __HUST_WING_ERROR_H__
#define __HUST_WING_ERROR_H__
#include "windows.h"
#define DllInterface __declspec(dllexport)
#ifdef __cplusplus
extern "C" {
#endif
DllInterface BOOL GetErrMsgByErrCode(DWORD dwErrCode,PTSTR sErrMsg);
#ifdef __cplusplus
}
#endif
#endif
--------------------------------------------------error.c------------------------------------------------------------------------
#include "stdio.h"
#include "error.h"
BOOL GetErrMsgByErrCode(DWORD dwErrCode,PTSTR psErrMsg)
{
HLOCAL hlocal = NULL;
BOOL bOK = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,dwErrCode, MAKELANGID(LANG_CHINESE_SIMPLIFIED,SUBLANG_CHINESE_SIMPLIFIED),
(PTSTR)&hlocal,0,NULL);
if (!bOK)
{
//是与网络相关的错误吗?
HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"),NULL,DONT_RESOLVE_DLL_REFERENCES);
if (hDll != NULL)
{
FormatMessage(FORMAT_MESSAGE_FROM_HMODULE|FORMAT_MESSAGE_ALLOCATE_BUFFER,
hDll,dwErrCode,MAKELANGID(LANG_CHINESE_SIMPLIFIED,SUBLANG_CHINESE_SIMPLIFIED),
(PTSTR)hlocal,0,NULL);
FreeLibrary(hDll);
}
}
if (hlocal != NULL)
{
//将字符串拷贝到psErrMsg中
lstrcpy(psErrMsg,(PTSTR)hlocal);
LocalFree(hlocal);
}
return TRUE;
}
---------------------------------------------------------------------------------------------------------------------------------------
在C#界面端,使用平台调用(p/Invoke)来调用ErrorLookup.dll,精华代码如下:
-----------------------------------------FrmError.cs--------------------------------------------------------------------------------------------
//public const Int32 FORMAT_MESSAGE_FORM_SYSTEM = 0x00001000;
//[DllImport("Kernel32.dll")]
//public static extern int FormatMessage(int flags, IntPtr source, int messageId, int languageId, StringBuilder buffer, int size, IntPtr arguments);
[DllImport("ErrorLookup.dll", CharSet = CharSet.Unicode, EntryPoint = "GetErrMsgByErrCode")]
public static extern bool GetErrMsgByErrCode(Int32 errCode, [MarshalAs(UnmanagedType.LPTStr)]StringBuilder errMsg);
private void tbErrCode_TextChanged(object sender, EventArgs e)
{
if (tbErrCode.Text == "")
{
return;
}
Int32 code = 0;
try
{
code = Convert.ToInt32(tbErrCode.Text);
}
catch (Exception) //InvalidCastException FarmatException etc.
{
tbErrorMsg.Text =tbErrCode.Text+ ": 将值内容转换为错误码出错!";
tbErrCode.Text = "";
return;
}
StringBuilder sbFormatMessage = new StringBuilder(1024);
//int retVal = FormatMessage(FORMAT_MESSAGE_FORM_SYSTEM, IntPtr.Zero, cd, 0,
// sbFormatMessage, sbFormatMessage.Capacity, IntPtr.Zero);
GetErrMsgByErrCode(code,sbFormatMessage);
if (!String.IsNullOrEmpty(sbFormatMessage.ToString()))
{
tbErrorMsg.Text =code.ToString() +":" + sbFormatMessage.ToString();
}
else
{
tbErrorMsg.Text = code.ToString() + ":" + "未找到与之对应的错误信息!";
}
}
-----------------------------------------------------------------------------------------------------------------------------------------------------
以上代码的注释部分还演示了如何直接使用p/Invoke来实现FormatMessage,而且这个时候,你也不再需要ErrorLookup.dll。代码和原理都很简单,但是在具体的编程实践上,还是有一些注意事项值得提醒,不然会浪费很多不必要的时间:
1.关于封送处理时的类型问题。Windows中的DWORD类型是unsigned long,但是切不可在封送处理的时候将其与.Net中的ulong对应起来,DWORD表示32位整数,而.Net中的ulong是64位整数(对应于Int64);所以在写封送函数签名的时候,DWORD对应Int32(int),而不是Int64(ulong)。
2.在封送处理StringBuilder的时候,不需要指定ref或out参数。具体内容和原理请参见MSDN杂志2008年1月刊:
CLR完全介绍:托管代码与非托管代码之间的封送处理。
网址:http://msdn.microsoft.com/zh-cn/magazine/cc164193.aspx(点进去吧,别怕,文章为简体中文:))
3.在编译ErrorLookup.dll的时候,在项目属性"字符集"里面选择"使用UNICODE字符集",否则调用时,显示出来的是乱码。