C++字符串完全指引之一 —— Win32 字符編碼
C++字符串完全指引之一 —— Win32 字符編碼
wchar_t wch = L''1''; // 2 bytes, 0x0031 wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters 字符在內存中是怎樣存儲的
Unicode的存儲形式,L"Bob"
使用兩個字節表示的0來做結束標誌。
值得注意的是,"ni"的值不能被解釋成WORD型值0xfa93,而應該看作兩個值93和fa以這種順序被作為"ni"的編碼。
因為x86CPU是little-endian,值0x0042在內存中的存儲形式是42 00。你能看出如果這個字符串被傳給strlen()函數會出現什麼問題嗎?它將先看到第一個字節42,然後是00,而00是字符串結束的標誌,於是strlen()將會返回1。如果把"Bob"傳給wcslen(),將會得出更壞的結果。wcslen()將會先看到0x6f42,然後是0x0062,然後一直讀到你的緩衝區的末尾,直到發現00 00結束標誌或者引起了GPF。 我們先來闡述規則2,因為找到一個違背它的真實的實例代碼是很容易的。假設你有一個程序在你自己的目錄裏保存了一個設置文件,你把安裝目錄保存在注冊表中。在運行時,你從注冊表中讀取安裝目錄,然後合成配置文件名,接著讀取該文件。假設,你的安裝目錄是C:/Program Files/MyCoolApp,那麼你合成的文件名應該是C:/Program Files/MyCoolApp/config.bin。當你進行測試時,你發現程序運行正常。 bool GetConfigFileName ( char* pszName, size_t nBuffSize ) { char szConfigFilename[MAX_PATH]; // Read install dir from registry... we''ll assume it succeeds. // Add on a backslash if it wasn''t present in the registry value. // First, get a pointer to the terminating zero. char* pLastChar = strchr ( szConfigFilename, ''/0'' ); // Now move it back one character. pLastChar--; if ( *pLastChar != ''//'' ) strcat ( szConfigFilename, "//" ); // Add on the name of the config file. strcat ( szConfigFilename, "config.bin" ); // If the caller''s buffer is big enough, return the filename. if ( strlen ( szConfigFilename ) >= nBuffSize ) return false; else { strcpy ( pszName, szConfigFilename ); return true; } }這是一段很健壯的代碼,然而在遇到 DBCS 字符時它將會出錯。讓我們來看看為什麼。假設一個日本用戶使用了你的程序,把它安裝在 C:/ ![]()
當使用 GetConfigFileName() 檢查尾部的''//''時,它尋找安裝目錄名中最後的非0字節,看它是等於''//''的,所以沒有重新增加一個''//''。結果是代碼返回了錯誤的文件名。 bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
// Read install dir from registry... we''ll assume it succeeds.
// Add on a backslash if it wasn''t present in the registry value.
// First, get a pointer to the terminating zero.
char* pLastChar = _mbschr ( szConfigFilename, ''/0'' );
// Now move it back one double-byte character.
pLastChar = CharPrev ( szConfigFilename, pLastChar );
if ( *pLastChar != ''//'' )
_mbscat ( szConfigFilename, "//" );
// Add on the name of the config file.
_mbscat ( szConfigFilename, "config.bin" );
// If the caller''s buffer is big enough, return the filename.
if ( _mbslen ( szInstallDir ) >= nBuffSize )
return false;
else
{
_mbscpy ( pszName, szConfigFilename );
return true;
}
}
上麵的函數使用CharPrev() API使pLastChar向後移動一個字符,這個字符可能是兩個字節長。在這個版本裏,if條件正常工作,因為lead byte永遠不會等於0x5c。讓我們來想象一個違背規則1的場合。例如,你可能要檢測一個用戶輸入的文件名是否多次出現了'':''。如果,你使用++操作來遍曆字符串,而不是使用CharNext(),你可能會發出不正確的錯誤警告如果恰巧有一個trail byte它的值的等於'':''的值。 與規則2相關的關於字符串索引的規則: 2a. 永遠不要使用減法去得到一個字符串的索引。 違背這條規則的代碼和違背規則2的代碼很相似。例如, char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1]; 這和向後移動一個指針是同樣的效果。 BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString ); BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); #ifdef UNICODE #define SetWindowText SetWindowTextW #else #define SetWindowText SetWindowTextA #endif當使用MBCS APIs來build程序時,UNICODE沒有被定義,所以預處理器看到: #define SetWindowText SetWindowTextA 這個宏定義把所有對SetWindowText的調用都轉換成真正的API函數SetWindowTextA。(當然,你可以直接調用SetWindowTextA() 或者 SetWindowTextW(),雖然你不必那麼做。) HWND hwnd = GetSomeWindowHandle(); char szNewText[] = "we love Bob!"; SetWindowText ( hwnd, szNewText ); 在預處理器把SetWindowText用SetWindowTextW來替換後,代碼變成: HWND hwnd = GetSomeWindowHandle(); char szNewText[] = "we love Bob!"; SetWindowTextW ( hwnd, szNewText ); 看到問題了嗎?我們把單字節字符串傳給了一個以Unicode字符串做參數的函數。解決這個問題的第一個方案是使用 #ifdef 來包含字符串變量的定義: HWND hwnd = GetSomeWindowHandle(); #ifdef UNICODE wchar_t szNewText[] = L"we love Bob!"; #else char szNewText[] = "we love Bob!"; #endif SetWindowText ( hwnd, szNewText ); 你可能已經感受到了這樣做將會使你多麼的頭疼。完美的解決方案是使用TCHAR. #ifdef UNICODE typedef wchar_t TCHAR; #else typedef char TCHAR; #endif 所以用MBCS來build時,TCHAR是char,使用UNICODE時,TCHAR是wchar_t。還有一個宏來處理定義Unicode字符串常量時所需的L前綴。 #ifdef UNICODE #define _T(x) L##x #else #define _T(x) x #endif ##是一個預處理操作符,它可以把兩個參數連在一起。如果你的代碼中需要字符串常量,在它前麵加上_T宏。如果你使用Unicode來build,它會在字符串常量前加上L前綴。 TCHAR szNewText[] = _T("we love Bob!"); 像是用宏來隱藏SetWindowTextA/W的細節一樣,還有很多可以供你使用的宏來實現str***()和_mbs***()等字符串函數。例如,你可以使用_tcsrchr宏來替換strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根據你預定義的宏是_MBCS還是UNICODE來擴展成正確的函數,就像SetWindowText所作的一樣。
何時使用 TCHAR 和 Unicode Windows 9x 中大多數的 API 沒有實現 Unicode 版本。所以,如果你的程序要在windows 9x中運行,你必須使用MBCS APIs。然而,由於NT係統內部都使用Unicode,所以使用Unicode APIs將會加快你的程序的運行速度。每次,你傳遞一個字符串調用MBCS API,操作係統會把這個字符串轉換成Unicode字符串,然後調用對應的Unicode API。如果一個字符串被返回,操作係統還要把它轉變回去。盡管這個轉換過程被高度優化了,但它對速度造成的損失是無法避免的。
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
作者簡介 Michael Dunn:居住在陽光城市洛杉磯。他是如此的喜歡這裏的天氣以致於想一生都住在這裏。他在4年級時開始編程,那時用的電腦是Apple //e。1995年,在 UCLA 獲得數學學士學位,隨後在Symantec 公司做 QA 工程師,在 Norton AntiVirus 組工作。他自學了 Windows 和 MFC 編程。1999-2000年,他設計並實現了 Norton AntiVirus 的新界麵。 Michael 現在在 Napster(一個提供在線訂閱音樂服務的公司)做開發工作,他還開發了UltraBar,一個IE工具欄插件,它可以使網絡搜索更加容易,給了 googlebar 以沉重打擊;他還開發了 CodeProject SearchBar;與人共同創建了 Zabersoft 公司,該公司在洛杉磯和丹麥的 Odense 都設有辦事處。 他喜歡玩遊戲。愛玩的遊戲有 pinball, bike riding,偶爾還玩 PS, Dreamcasth 和 MAME 遊戲。他因忘了自己曾經學過的語言:法語、漢語、日語而感到悲哀。 |
最後更新:2017-04-02 06:51:20