一.错误处理
-Microsoft编辑了一个列表,其中列出了所有可能的错误代码,并为每个错误代码都分配了一个32位的编号。
-在内部,Windows函数检测到错误时,使用“线程本地存储区”,将相应的错误代码和“主调线程”关联到一起。
-查看错误代码:
// 返回由上一个函数调用设置的线程的32位错误代码
DWORD GetLastError();
-错误代码信息:
WinError.h头文件包含了Microsoft定义的错误代码列表。
每个错误有三种表示:
消息ID
消息描述
ID具体值
-错误码其它说明:
1.一些Windows函数调用成功可能源于不同原因,借助错误代码可以帮助进一步区分成功的原因。对具有这种行为的函数Platform SDK文档会说明。
2.Windows函数失败后,应马上调用GetLastError,如又调用了另一个Windows函数,此值可能被改写。
-将错误代码转换为相应的文本描述
1.利用Visual Studio的Error Lookup
2.代码
DWORD FormatMessage(
DWORD dwFlags,// 1
LPCVOID pSource,// 2
DWORD dwMessageId,// 3
DWORD dwLanguageId,// 4
PTSTR pszBuffer,// 5
DWORD nSize,// 6
va_list *Arguments// 7
);
1.
FORMAT_MESSAGE_ALLOCATE_BUFFER 要求函数分配一块足以容纳错误文本描述的内存
FORMAT_MESSAGE_FROM_SYSTEM 从系统错误码中查找匹配
FORMAT_MESSAGE_FROM_HMODULE 从指定模块错误码中查找匹配
FORMAT_MESSAGE_IGNORE_INSERTS 允许我们获得含%占位符的消息
2.指定FORMAT_MESSAGE_FROM_HMODULE 时是对应的模块句柄
3.错误码
4.0,操作系统默认语言
5.接收缓冲区地址。(缓冲区内存由函数内部分配,后续需要依据此地址进行内存释放。)
6.FORMAT_MESSAGE_ALLOCATE_BUFFER 时,尺寸信息无意义,设为0即可。
7.NULL
-设置自定义的错误代码
Windows函数失败时,设置错误码来指出错误。
Microsoft还允许将这种机制用到我们自己的函数中。
设置线程的上一个错误代码:
VOID SetLastError(DWORD dwErrorCode);
-错误代码分析:
位 | 31-30 | 29 | 28 | 27-16 | 15-0 |
---|---|---|---|---|---|
内容 | 严重性 | Microsoft/客户 | 保留 | 设备码 | 异常代码 |
含义 | 0,成功1,提示2,警告3,错误 | 0,Microsoft 1,客户 | 0 | 前256个值由Microsoft保留 |
二.字符和字符串处理
-建议:
1.在应用程序中始终使用Unicode字符串。
2.始终通过新的安全字符串函数来处理这些字符串。
-Unicode字符的UTF-16编码
1.每个字符编码为2个字节。
2.对于16位不足以表示某些语言所有字符的情况,支持使用代理。代理是用32位来表示一个字符的一种方式。
UFT-8,UTF-32
-Unicode字符集和字母表
6位代码 | 字符 |
---|---|
0000-007F | ASCII |
0080-00FF | 西欧语系字母 |
0100-017F | 欧洲拉丁字母 |
0180-01FF | 扩充拉丁字母 |
0250-02AF | 标准音标 |
02B0-02FF | 进格修饰字母 |
0300-036F | 常见的变音符号 |
0400-04FF | 西里尔字母 |
0530-058F | 亚美尼亚文 |
0590-05FF | 希伯来文 |
0600-06FF | 阿拉伯文 |
0900-097F | 梵文字母 |
-ANSI字符和Unicode字符与字符串数据类型
1.C语言的char表示一个8位ANSI字符。
2.Microsoft的c/c++编译器定义了一个内建的数据类型wchar_t,它表示一个16位的Unicode(UTF-16)字符。
早期的Microsoft编译器没有提供这个内建的数据类型,编译器只在指定了/Zc:wchar_t编译器开关时,才会定义这个数据类型。默认时,在Micorsoft Visual Studio中新建一个c++项目时,这个编译器开关是指定的。
3.声明
char c = 'A';
char szBuffer[100] = "A String";
wchar_t c = L'A';
wchar_t szBuffer[100] = L"A String";
L通知编译器该字符串应该编译为一个Unicode字符串。当编译器将此字符串放入程序的数据段时,会使用UTF-16来编码每个字符。
4.为了与C语言稍微有一点区分,Windows头文件WinNT.h中定义了以下数据类型
typedef char CHAR;
typedef wchar_t WCHAR;
5.前缀__nullterminated是一个头部注解,描述了一个类型如何用作函数的参数和返回值。
6.
#ifdef UNICODE
typedef WCHAR TCHAR, *PTCHAR, *PTSTR;
typedef CONST WCHAR *PCTSTR;
#define __TEXT(quote) L##quote
#else
typedef CHAR TCHAT, *PTCHAR, *PTSTR;
typedef CONST CHAR *PCTSTR;
#define __TEXT(quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
利用这些类型和宏写代码,无论使用ANSI还是Unicode字符,都能通过编译。
-Windosw中的Unicode函数和ANSI函数
1.Windows的一些API函数一般提供Unicode和非Unicode版本。
2.一般Unicode版本做具体实现,非Unicode版本,先把ANSI转换为
Unicode再调用Unicode版本的实现。
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif
-C运行库中的Unicode函数和ANSI函数
1.C运行库提供了一系列函数来处理ANSI字符和字符串,并提供了另一系列函数来处理Unicode字符与字符串。但,ANSI版本也做具体实现。
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
-C运行库中的安全字符串函数
1.C运行库的一些字符串函数是不安全的。
2.Micorsoft提供了一系列新的函数来取代C运行库的不安全的字符串处理函数。
3.应使用Microsoft在StrSafe.h中定义的新的安全字符串函数。
-初识新的安全字符串函数
1.C运行库现有的字符串处理函数已被标记为废弃不用,如使用,编译器报警告。
2.须在包含其它所有文件后,才包含StrSafe.h.
3.现有的每一个函数(如_tcscpy)都有一个对应的新版本的函数,前面名称相同但最后添加了一个_s后缀。
PTSTR _tcscpy (PTSTR strDestination, PCTSTR strSource);
errno_t _tcscpy_s(PTSTR strDestination, size_t numberOfCharacters, PCTSTR strSource);
4.安全字符串函数流程
所有安全字符串函数(_s后缀)的首要任务是验证传给它们的参数值。
要检查的项目包括:
指针不为NULL
整数在有效范围内
枚举值是有效的
缓冲区足以容纳结果数据
如果这些检查中的任何一项失败,函数都会设置局部于线程的C运行时变量errno。并返回一个errno_t值来指出成功或失败。然而,这些函数并不实际返回。而是直接终止应用。
C运行时,实际上允许我们提供自己的函数,在检测到一个无效参数时,会调用此函数。
5.自定义错误处理
5.1.定义一个如下原型的函数
void InvalidParameterHandler(
PCTSTR expression,// C运行时实现代码中可能出现的调用失败
PCTSTR function, // 错误函数名
PCTSTR file, // 源代码文件
unsigned int line, // 源代码行号
uintptr_t);
5.2.注册处理程序
_set_invalid_parameter_handler
5.3.禁止由C运行时,触发Debug Assertion Failed对话框
在应用程序开头处,调用
_CrtSetReportMode(_CRT_ASSERT, 0);
在执行了以上三步后,现在在调用String.h中的替代函数时,可以检查返回的errno_t值,只有返回S_OK,才是成功的。其它可能的返回值在errno.h中有定义。
6.举例
TCHAR szBuffer[10] = {TEXT('-'), ..., TEXT('-')};
errno_t result = _tcscpy_s(szBuffer, _countof(szBuffer), TEXT("0123456789"));
返回值:ERANGE
szBuffer:首字符设置为’\0’,后续每个字节设置为0xfd。
-在处理字符串时,获得更多控制
1.除了新的字符串函数,C运行库还新增了一些函数,用于在执行字符串处理时提供更多控制。包含在StrSafe.h中。
如,控制填充符,如何截断。
HRESULT StringCchCat(
PTSTR pszDest,
size_t cchDest,
PCTSTR pszSrc,
);
HRESULT StringCchCatEx(
PTSTR szDest,
size_t cchDest,
PCTSTR pszSrc,
PTSTR* ppszDestEnd,
size_t *pcchRemaining,
DWORD dwFlags
);
Cch表示字符数。
还有一系列名称中含Cb的函数,表示用字节数来指定大小。
关于返回值:
S_OK:成功。
STRSAFE_E_INVALID_PARAMETER:失败,将NULL值传给了参数。
STRSAFE_E_INSUFFICIENT_BUFFER:失败,目标缓冲区太小。
不同于_s后缀的字符串函数,当缓冲区太小的时候,这些函数会执行截断。缓冲区太小时,新的函数中,源缓冲区中可以装入目标可写缓冲区的那一部分会被复制,而且最后一个可用字符会被设为’\0’。故前面举例中的结果将变成”012345678”。
2.许多新的函数都有扩展版本。这些版本有三个额外的参数。
size_t * pcchRemaining:
目标缓冲区还有多少字符尚未使用。不考虑终止符。
LPTSTR* ppszDestEnd;
指向终止符位置
DWORD dwFlags;
STRSAFE_FILL_BEHIND_NULL 成功时,dwFlags低字节填充目标缓冲区剩余部分。
STRSAFE_IGNORE_NULLS 把NULL字符串指针视为空字符串
STRSAFE_FILL_ON_FAILURE 失败时,dwFlags低字节填充整个目标缓冲区。目标缓冲区最后一个字节设为’\0’。
STRSAFE_NULL_ON_FAILURE 失败时,首字符设为’\0’
STRSAFE_NO_TRUNCATION 失败时,首字符设为’\0’
-Windows字符串函数
比较字符串
int CompareString
(
// 语言标识,检查字符在所标识语言中的含义。以符合语言习惯的方式比较
LCID locale,
// NORM_IGNORECASE 忽略大小写
// ...
DWORD dwCmdFlags,
PCTSTR pString1,
// 字符数,如为负数,自动计算长度
int cch1,
PCTSTR pString2,
int cch2
)
// 得到主调线程语言标识
LCID GetThreadLocale();
int CompareStringOrdinal
(
PCWSTR pString1,
int cchCount1,
PCWSTR pString2,
int cchCount2,
BOOL bIgnoreCase
)
-建议
1.始终使用安全的字符串处理函数,如后缀为_s的函数,或前缀为StringCch的函数。后者主要在我们想明确控制截断时使用。
2.不要用Kernel32中的字符串处理,如lstrcat。不要用C运行库中不安全的字符串处理函数。
3.比较文件名/路径/XML元素/属性/注册表项登,用CompareStringOrdinal。比较界面显示字符串用CompareString。
-Unicode与ANSI字符串转换
int MultiByteToWideChar
(
// 标识了与多字节字符串关联的代码页值
UINT nCodePage,
// 一般传0
DWORD dwFlags,
//
PCSTR pMultiByteStr,
// 字节数。-1,自动计算。
int cbMultiByte,
PWSTR pWideCharStr,
// 目标缓冲区最大可用字符数。0,不执行转换,仅返回宽字符数(含终止符'\0')。
// 目标缓冲区需要不小于此字符数。
int cchWideChar
)
1.多字节字符串转换为Unicode步骤
调MultiByteToWideChar,为pWideCharStr传入NULL,为cchWideChar传入0,为cbMultiByte传-1。
分配可容纳转换后Unicode字符串的内存。
调MultiByteToWideChar
int WideCharToMultiByte
(
UINT uCodePage,
DWORD dwFlags,
PCWSTR pWideCharStr,
// 字符数,-1,自动计算。
int cchWideChar,
PSTR pMultiByteStr,
// 目标缓冲区最大可用字节数。0,不执行转换,仅返回字节数(含终止符'\0')。
// 目标缓冲区需要不小于此字符数。
int cbMultiByte,
// 一个字符在uCodePage指定的代码页中没有对应的表示时,用此做替代符
PCSTR pDefaultChar,
// 如宽字符串中至少有一个字符不能转换为对应的多字节形式,此变量设为TRUE。否则,FALSE。
PBOOL pfUsedDefaultChar
)
-导出ANSI和Unicode DLL函数
// .cpp
BOOL StringReverseW(PWSTR pWideCharStr, DWORD cchLength)
{
PWSTR pEndOfStr = pWideCharStr + wcsnlen_s(pWideCharStr, cchLength) - 1;
wchar_t cCharT;
while(pWideCharStr < pEndOfStr)
{
cCharT = *pWideCharStr;
*pWideCharStr = *pEndOfStr;
*pEndOfStr = cCharT;
pWideCharStr++;
pEndOfStr--;
}
return TRUE;
}
BOOL StringReverseA(PSTR pMultiByteStr, DWORD cchLength)
{
PWSTR pWideCharStr;
int nLenOfWideCharStr;
BOOL fOK = FALSE;
nLenOfWideCharStr = MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, cchLength, NULL, 0);
pWideCharStr = (PWSTR)HeapAlloc(GetProcessHeap(), 0, nLenOfWideCharStr * sizeof(wchar_t));
if(pWideCharStr == NULL)
{
return fOK;
}
MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, cchLength, pWideCharStr, nLenOfWideCharStr);
fOK = StringReverseW(pWideCharStr, cchLength);
if(fOK)
{
WideCharToMultiByte(CP_ACP, 0, pWideCharStr, cchLength, pMultiByteStr, (int)strlen(pMultiByteStr), NULL, NULL);
}
HeapFree(GetProcessHeap(), 0, pWideCharStr);
return fOK;
}
// .h
BOOL StringReverseW(PWSTR pWideCharStr, DWORD cchLength);
BOOL StringReverseA(PSTR pMultiByteStr, DWORD cchLength);
#ifdef UNICODE
#define StringReverse StringReverseW
#else
#define StringReverse StringReverseA
#endif
-判断文本文件是ANSI还是Unicode
// IsTextUnicode有助于进行这种分辨
// 使用一系列统计性和确定性方法来猜测缓冲区的内容。
// 这种方法并不精确,可能返回错误结果
// TRUE Unicode
// FALSE 非Unicode
BOOL IsTextUnicode
(
CONST PVOID pvBuffer,
int cb, // 字节数
// NULL 执行全部测试
// 指定了具体的测试项目,函数返回前会设置整数中的相应位,
// 以反映每个测试项目的结果
PINT pResult
);