编码 |
字节数 |
类型 |
字符(串)常量 |
WinNT.h中的定义 |
ANSI |
8bit |
char |
'A' "A string" |
typedef char CHAR; typedef CHAR *PCHAR; typedef CHAR *PSTR; typedef CONST CHAR *PCSTR; |
Unicode (UTF-16) |
16bit |
wchar_t 通过编译器设置/Zc:wchar_t支持 |
L'A' L"A string" |
typedef char CHAR; typedef CHAR *PWCHAR; typedef CHAR *PWSTR; typedef CONST CHAR *PCWSTR; |
WinNT.h头文件中定义字符类型宏,TCHAR、PTCHAR、PTSTR、PCTSTR、TEXT(),可在ANSI和Unicode编码间通用。
2. Windows函数中关于字符编码的信息
- 使用UNICODE宏区分程序的编码是使用ANSI或Unicode;
- 在Vista中ANSI编码的函数在内部首先将ANSI转化为Unicode再调用Unicode版本去做;
- 为兼容16位的旧Windows函数只有ANSI编码版本,应被替换掉,如OpenFile -> CreateFile, WinExec -> CreateProcess等;
- 新的Windows函数只有Unicode版本,如ReadDirectoryChangesW和CreateProcessWithLogonW;
- COM接口要求使用Unicode;
- 资源文件通常为Unicode编码,Vista下系统可自动转换编码。
3. C运行时库中关于字符编码的信息
- C运行时库提供Unicode、ANSI两个版本,但两者不会相互调用;
- 使用_UNICODE宏区分Unicode和ANSI编码。
4. C运行时库的安全字符函数
潜在隐患:目标字符缓冲区未能承载结果字符,导致内存崩溃。
解决方法:包含StrSafe.h头文件
a. 使用_tcscpy_s、_tcscat_s代替_tcscpy、_tcscat,其函数原型为
errno_t _tcscpy_s(PTSTR strDestination, size_t numberofCharacters, PCSTR strSource);
errno_t _tcscat_s(PTSTR strDestination, size_t numberofCharacters, PCSTR strSource);
- 其中参数numberofCharacters使用_countof宏(在stdlib.h中定义)计算字符的数目去确定缓存大小;
- 安全函数返回errno_t,但并非真正返回。在Debug模式下编译的程序会弹出一断言对话框,Release模式下编译的程序会直接退出。
错误处理方法:
① 定义一错误处理函数,函数原型如下:
void InvalidParameterHandler(PCSTR expression, PCSTR function, PCSTR file, unsigned in line, uintptr_t /*pReserved*/);
② 使用_set_invalid_parameter_handler注册处理函数;
③ 程序开始时,调用_CrtSetReportMode(_CRT_ASSERT, 0)关闭断言对话框。
- 当字符串经过安全函数处理时出现错误后,字符串首字符变为'\0',缓冲区的其余部分由0xfd填充。
b. 更多对字符处理的控制
包括以字符数目计算缓冲区大小的函数,函数名中带有Cch(count of characters)
StringCchCat(Ex),StringCchCopy(Ex),StringCchPrintf(Ex)
和以字节数计算缓冲区大小的函数,函数名中带有Cb(count of bytes)
StringCbCat(Ex),StringCbCopy(Ex),StringCbPrintf(Ex)
- 函数返回值类型为HRESULT
S_OK:表示字符处理成功;
STRSAFE_E_INVALID_PARAMETER:非法参数,如pszDest为NULL、错误的dwFlags等;
STRSAFE_E_INSUFFICIENT_BUFFER
:目标字符串缓冲区空间不足,但目标字符串会尽可能放入字符并以'\0'结束字符串。
- 以Ex结尾的字符串处理函数提供了更多的控制,增加了三个参数:
LPTSTR* ppszDestEnd:返回目的字符串缓冲区结束字符('\0')的字符指针;
size_t* pcchRemaining:返回目的字符串缓冲区未使用字符数;
DWORD dwFlags:关于字符处理的标记。
数值 |
描述 |
STRSAFE_FILL_BEHIND_NULL |
当函数运行成功时,将dwFlags低字节填充到目的字符串未初始化的字符上。 |
STRSAFE_IGNORE_NULLS |
将NULL字符串指针视为(TEXT(""))。 |
STRSAFE_FILL_ON_FAILURE |
当函数运行失败时,将dwFlags低字节填充到整个目的字符串上,除了第一个字符设置为'\0'。 |
STRSAFE_NULL_ON_FAILURE |
当函数运行失败时,将目的字符串的第一个字符设置为'\0',并将字符串视为(TEXT(""))。 |
STRSAFE_NO_TRUNCATION |
目的字符串被设置为(TEXT(""))。 |
c. Windows字符函数关于字符串比较的函数
可使用CompareString(Ex)和CompareStringOrdinal比较字符串:
- CompareString(Ex)是根据系统的区域设置,从语言学角度去比较两个字符串。函数需要获得一32位的区域设置编号(LCID),可通过GetThreadLocale函数获得。
- CompareStringOrdinal则是针对程序代码中的字符串比较,只做码点比较(code-point comparison)。该函数只有Unicode编码的版本。
- 返回值为int。可将返回值减2,即可得到与C运行时库中字符串比较函数相同的返回值。
宏 |
数值 |
描述 |
0 |
字符串比较操作失败。 |
|
CSTR_LESS_THAN |
1 |
pString1小于pString2。 |
CSTR_EQUAL |
2 |
字符串相等。 |
CSTR_GREATER_THAN |
3 |
pString1大于pString2。 |
5. 使用Unicode的理由
- 便于程序的全球化和本地化;
- 可发布独立的支持所有语言的二进制文件;
- 提高程序运行的效率和减少内存占用(部分Windows函数会在内部将ANSI字符串转为Unicode再继续运行);
- 某些Windows函数只提供Unicode编码版本;
- 可确保程序能够与COM整合;
- 可确保程序能够与.Net Framework整合;
- 便于程序资源操作(资源中的字符串通常为Unicode编码)。
6. 使用字符及字符串的推荐方法
- 以字母数组的角度,而非char数组和字节数组的角度去看待字符串;
- 对于文本字符和字符串,使用通用的数据类型,如TCHAR/PTSTR;
- 对于字节,字节指针和数据缓冲区,使用明确的数据类型,如BYTE/PBYTE;
- 对于原义字符及字符串使用TEXT()或_T()宏,但不要混合使用;
- 全球化,如使用PTSTR代替PSTR;
- 使用_countof()宏计算字符个数,多用于确定缓冲区大小。使用_sizeof()计算字节数,多用于分配内存空间;
- 避免printf系列函数,特别是使用%s和%S去实现ANSI和Unicode相互转换,应使用MultiByteToWideChar和WideCharToMultiByte;
- 总是同时使用或不使用UNICODE和_UNICODE宏。
7. 操作字符串的推荐方法
- 总是使用安全函数(以_s为后缀或以StringCch为前缀的函数);
- 不要使用不安全的C运行时库函数,即没有以目标字符串大小为函数参数的函数。
对于缓冲区的操作,C运行时库提供了相应的更新版本,例如memcpy_s,memmove_s,wmemcpy_s,wmemmove_s。使用这些函数,需要声明__STDC_WANT_SECURE_LIB__宏和包含CrtDefs.h;
- 使用编译器设置/GS和/RTCs;
- 不使用Kernel32中的方法,例如lstrcat和lstrcpy;
- 程序代码中使用的字符串的比较使用CompareStringOrdinal,用户界面上的字符串比较使用CompareString(Ex)。
8. Unicode与ANSI的相互转化
- 使用MultiByteToWideChar,将多字节字符串转化为宽字符串,函数原型为:
int MultiByteToWideChar(UINT uCodePage, DWORD dwFlag, PCSTR pMultiByteStr, int cbMultiByte, PWSTR pWideCharStr, int cchWideChar)
转换步骤:
① 以NULL为pWideCharStr的值、0为cchWideChar的值、-1为cbMultiByte的值调用MultiByteToWideChar,由返回值得到源字符串的字符个数;
② 分配内存,大小为源字符串的字符个数×sizeof(wchar_t);
③ 再次调用MultiByteToWideChar,将目的字符串缓冲区地址、字符个数等参数传入;
④ 使用字符;
⑤ 释放字符。
- 使用WideCharToMultiByte,将宽字符串转化为多字节字符串,函数原型为:
int WideCharToMultiByte(UINT uCodePage, DWORD dwFlag, PCWSTR pWideCharStr, int cchWideChar, PSTR pMultiByteStr, int cbMultiByte, PCSTR pDefaultChar, PBOOL pfUsedDefaultChar)
转换步骤:
与MultiByteToWideChar类似。参数pDef
aultChar作为默认字符,用于替代未能在多字节字符中找到对应字符的宽字符,pfUsedDefaultChar用于返回是否有宽字符未能找到对应的多字节字符。
- 通过MultiByteToWideChar和WideCharToMultiByte函数,在实现时只实现Unicode编码版本的函数。通过内部调用MultiByteToWideChar转换再调用Unicode编码版本的方式,实现ANSI编码版本的函数。最后使用宏的方式统一函数命名。
在CPP文件中:
void FuncW(PWSTR pWideCharStr, DWORD cchLength) { ...... } void FuncA(PSTR pMultiByteStr, DWORD cchLength) { PWSTR pWideCharStr; int nLenOfWideCharStr; nLenOfWideCharStr = MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, cchLength, NULL, 0); pWideCharStr = (PWSTR) HeapAlloc(GetProcessHeap(), 0, nLenOfWideCharStr * sizeof(wchar_t)); If (pWideCharStr == NULL) return; MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, cchLength, pWideCharStr, nLenOfWideCharStr); FuncW(pWideCharStr, cchLength); WideCharToMultiByte(CP_ACP, 0, pWideCharStr, cchLength, pMultiByteStr, (int)strlen(pMultiByteStr), NULL, NULL); HeapFree(GetProcessHeap(), 0, pWideCharStr); return; }
在头文件中:
void FuncW(PWSTR pWideCharStr, DWORD cchLength); void FuncA(PSTR pMultiByteStr, DWORD cchLength); #ifdef UNICODE #define Func FuncW #else #define Func FuncA #endif
- 区分Unicode与ANSI编码
使用AdvApi32.dll中,在WinBase.h中定义的IsTextUnicode函数去区分文件使用的是ANSI还是Unicode编码。该函数并未能精确得出文件的编码。其函数原型为:
BOOL IsTextUnicode(CONST PVOID pvBuffer, int cb, PINT pResult);