知识点滴

知识是一点一滴地积累的

 

字符与字符串操作——Windows via C/C++

在最新版的Windows, Windows Vista,它应该支持Unicode 5.0。在编程中对字符与字符串的操作是很普通的,为新的系统写代码,尽可能使用Unicode,它提供了更好的性能,以及可以进行区域化。而且与COM及.Net框架互操作时也有帮助。
缓冲区溢出是系统漏洞的重要来源,Microsoft对此提供的c-runtime中包含了一些新函数用于操作字符串。你应该都使用这些新函数。
在Windows Vista中,每个字符都用UTF-16(UTF是Unicode Transformation Format)进行编码,它是双字节的,这里我们所指的Unicode都指UTF-16编码。但是双字节还不能表示某些语言中的所有字符,对于这些语言,它支持代理(surrogates),后者是用32位代表一个字符。
还有另外一些UTF编码,常见的有:
UTF-8:它对一些字符用1byte,一些字符用2byte,一些字符用3或4byte表示。这种编码目前很流行。
UTF-32:它对每个字符都用4byte表示,这种表示法效率低,很少使用。

一. 数据类型
对于单字节与双字节字符,它定义分别如下:
char c='a';
char szBuffer[100] = "A string";

wchar_t c=L'a';
wchar_t szBuffer[100] = L"A string";
实际上,wchar_t是unsigned short类型,另外Microsoft还定义了CHAR, PCHAR, TEXT等类型和宏,这些都是条件定义的,它根据是否使用Unicode而决定合适的版本。这些定义可以在WinNT.h文件找到。

二. Unicode和ANSI函数
从Windows NT开始,所有的核心API函数都使用Unicode版本,所以都要求Unicode字符串。如果你传递的是ANSI字符串,则它要进行转换,它会消耗性能的。在之前的Windows版本,很多函数都提供两个版本:XXXW和WWWA,如CreateWindowExW和CreateWindowExA。到Vista,后者只是起一个转换层的作用。如果你需要建立DLL让别人使用,也可以考虑
同样,C-runtime也提供了Unicode版本的相应字符串函数,如strlen对应的wcslen(wc表示wide character)。但是尽管这样,c-runtime函数对于字符串操作是非常不安全的,如下:
WCHAR  szBuffer[3] = L"";
wcscpy(szBuffer, L"abc");
上面代码把四个字符拷贝到只有三个字符长的缓冲区中!这些代码是缓冲区溢出的最大根源。对此,Microsoft提供了安全字符串函数,它在strsafe.h(在Windows SDK中,而不在VC的目录中)中声明(这个头文件应该在最后包括,因为它使用了其它头文件)
PTSTR _tcscpy(PTSTR strDes, PCTSTR strSrc); //
errno_t _tcscpy_s(PTSTR strDes, size_t numChars, PCTSTR strSrc);

除了这些函数外,C运行时还提供了对字符串操作有更多控制的函数。如:
HRESULT StringCchCat(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc);
HRESULT StringCchCatEx(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc,
   PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags);

HRESULT StringCchCopy(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc);
HRESULT StringCchCopyEx(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc,
   PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags);

HRESULT StringCchPrintf(PTSTR pszDest, size_t cchDest,
   PCTSTR pszFormat, ...);
HRESULT StringCchPrintfEx(PTSTR pszDest, size_t cchDest,
   PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags,
   PCTSTR pszFormat,...);
在这些参数中,Cch表示Count of Characters。另外还有一个以Cb作为函数名的字符串操作函数集,这里Cb指Count of byte,如StringCbCat(Ex), StringCbCopy(Ex)。
在这些函数中,如果缓冲区太小,它不会完成功能并返回STRSAFE_E_INSUFFICIENT_BUFFER。在扩展版本的函数中(Ex),额外的参数的意思如下:
size_t* pcchRemaining: 目标缓冲区中未用字符的数目('\0'不计入),例如把一个字符拷贝到10长度的缓冲区中,就得到9。
LPTSTR* ppszDestEnd: 如果它非空,指向目标缓冲区的'\0'字符。
DWORD dwFlags: 一些枚举值组合。见SDK文档。

三. Windows字符串函数
Windows也提供了许多字符串操作函数,一些函数如lstrcat, lstrcpy等已经过时了,因为它们没有提供缓冲区溢出检测。另外ShlwApi.h还定义了许多格式化操作的字符串函数,特别是数字值,如StrFormatKBSize和StrFormatByteSize等。见MSDN。
比较字符中的操作很常见,这个任务最好调用CompareString(Ex)和CompareStringOrdinal:
int CompareString(
   LCID locale,
   DWORD dwCmdFlags,
   PCTSTR pString1,
   int cch1,
   PCTSTR pString2, int cch2);
它还区域信息作为比较依据,一般会取得正确的比较结果。这里LCID一般通过GetThreadLocale函数取得当前线程的区域ID。当然这个函数效率上会慢,对于比较程序性字符串,如参数,注册表键,XML元素属性等,可以使用CompareStringOrdinal,这不考虑区域信息。
要注意这两个函数返回值与C run-time的相应函数不同,它是1(CSTR_LESS_THAN), 2(CSTR_EQUAL)和3(CSTR_GREATER_THAN),你可以减2获得与c run-time相应的返回值。

四. 编程建议
1. 把字符串作为字符数组,而不是char数组或byte数组
2. 使用通用类型(TCHAR/PTSTR)表示字符和字符串
3. 对于字节,字节指针和数据缓冲区使用直接类型BYTE和PBYTE
4. 使用字符串和字符常量使用TEXT或_T宏
5. 进行全局替换,如用PTSTR替换PSTR
6. 修改字符串相关的数字运算问题,如用_countof(szBuffer)而不是sizeof(szBuffer)取得缓冲区的字符数。分配内存也用malloc(nch*sizeof(TCHAR))而不用malloc(nch),当然可以定义如下的宏:
#define chmalloc(nCharacters) (TCHAR*)malloc(nCharacters * sizeof(TCHAR))
7. 避免printf家族函数,特别是%s和%S类型的参数,具体见转换部分
8. 要指定UNICODE符号
下面是字符串操作函数的选择建议
1. 使用安全字符串版本(带_s后缀或StringCch前缀的)。不使用任何没有带目标缓冲区大小的缓冲区操作函数。c run-time提供了memcpy_s, memmove_s等函数使用
2. 利用/GS和/RTCs编译器选取项来自动检测缓冲区溢出问题
3. 不使用Kernel32中的字符串操作函数,如lstrcat, lstrcpy

五. 在Unicode和ANSI间转换
Windows函数MultiByteToWideChar提供了多字节字符(非Unicode的多字节字符)与Unicode字符的转换。
int MultiByteToWideChar(
   UINT uCodePage,
   DWORD dwFlags,
   PCSTR pMultiByteStr,
   int cbMultiByte,
   PWSTR pWideCharStr,
   int cchWideChar);
int WideCharToMultiByte(
   UINT uCodePage,
   DWORD dwFlags,
   PCWSTR pWideCharStr,
   int cchWideChar,
   PSTR pMultiByteStr,
   int cbMultiByte,
   PCSTR pDefaultChar,
   PBOOL pfUsedDefaultChar);

函数的使用过程一般如下:
1. 参数pWideCharStr传递NULL,cchWideChar参数传递0,cbMultiByte传递-1,这样调用就取转换后的字符数
2. 根据1返回的字符数(cc)分配内存块(cc*sizeof(wchar_t))
3. 再调用该函数,传入相应参数进行字符串转换
4. 释放2中产生的内存块

如果你需要编写两种类型的字符串都能操作的DLL函数,你也可以Microsoft一样提供XXXW和XXXA版本的函数,其中后者的实现在经过上面两个函数转换后传递给前一个函数的实现。如果要决定某个文件的字符是否是Unicode字符,可用IsTextUnicode函数:
BOOL IsTextUnicode(CONST PVOID pvBuffer, int cb, PINT pResult);
它声明于AdvApi32.dll中,但是这个函数是基于统计的,这样可能会返回一个不正确的结果。

posted on 2009-10-11 10:25  阿东  阅读(1960)  评论(0编辑  收藏  举报

导航