爲了程序編寫方便,根除亂碼問題等等需求,不少新項目都採用了Unicode編碼。
同時,很多使用MBCS多字節編碼的舊項目爲了升級,也有了轉向Unicode編碼的意向。
不過,從MBCS升級到Unicode並非無縫的,該問題的複雜程度,取決於代碼總量和
代碼的編寫質量。
本文是做者在一個C/C++項目中的一些經驗之談,但願對有此需求的讀者帶來幫助。程序員
1. 工程屬性切換在VC6.0中,切換到Unicode沒有直接的選項能夠選,須要在宏定義中添加UNICODE和_UNICODE,
同時須要去除MBCS宏定義。另外,若是生成的是exe的程序的話,還須要定義入口函數名wWinMainCRTStartup。
在VC2003以及以後的IDE環境中,有直接選者使用UNICODE仍是MBCS的選項,無需添加宏定義。
【設定場所】
VC6.0: 主菜單 - 工程 - 設定 - C/C++標籤(共通) - 宏定義
主菜單 - 工程 - 設定 - 連接標籤(輸出) - 入口符號
VC2003: 主菜單 - 工程 - 屬性 - 共通 - 字符集數據庫
2. 字符串定義編程
MBCS | Unicode | 兼容MBCS和Unicode |
char | WCHAR | TCHAR |
char* | LPWSTR | LPTSTR |
LPSTR | LPWSTR | LPTSTR |
const char* | LPCWSTR | LPCTSTR |
LPCSTR | LPCWSTR | LPCTSTR |
※兼容MBCS和Unicode: 根據第一步的工程屬性切換來決定採用MBCS仍是Unicode
是一種比較好的,切換後無需修改代碼的類型定義。不過也會帶來必定的麻煩(稍後17點會針對這點進行討論)。安全
3. 字符串常量定義須要在字符串兩端加上L...,兼容模式的話則是_T(...)或者TEXT(...),好比:ide
MBCS | Unicode | 兼容MBCS和Unicode |
"Clannad" | L"Clannad" | _T("Clannad")或者TEXT("Clannad") |
※對於宏定義__FILE__,若是要使得這個宏定義變成Unicode的話,必須使用TEXT(...)
採用_T(...)會產生編譯錯誤。函數
4. 字符串文字數計算在MBCS中,咱們採用strlen來計算一個字符串的長度,其實結果是字節數,而非文字數(純英文數字除外)。
而在Unicode中,咱們能夠採用lstrlen來計算一個字符串的長度,其結果是文字數,剛好是字節數的一半。
但有時候咱們須要得到變量能夠容納文字數的長度,好比LoadString(),其中最後一個參數須要咱們傳入
可容納最大的文字數長度,在MBCS中,咱們經常這麼寫sizeof(buffer),不過在Unicode中,
這樣寫的話就有可能會致使內存溢出,因此更好的寫法是sizeof(buffer) / sizeof(TCHAR)或者
sizeof(buffer) / sizeof(buffer[0])
我的推薦後者,理由是仍然在Unicode環境下,若是buffer由於某些緣由從TCHAR變回了char,
那麼後者能正常工做,前者會由於錯誤的字符串變量文字數而致使字符串截斷。
※另外須要注意的是,在Windows API中若是是針對內存操做的函數,好比memcpy, memset等等,
那麼sizeof(buffer)是正確的,由於函數須要傳入字節數,而不是文字數
只有在API函數須要的文字數的時候才須要做此更改,對於參數的詳細信息,能夠參考MSDN後再作判斷編碼
5. 字符串函數的替換最先的一些字符串處理函數,好比: strcpy, strlen等等由於都是針對ANSI的(strstr這類搜索函數
在處理MBCS字符串時可能會產生錯誤,因此這些函數自己並非MBCS向的),在更換成Unicode後,
這些經常使用函數也多了許多新版本,不僅僅是針對Unicode,並且增長安全性等方面也做了改進。
在這裏列出來的話可能會佔用很多篇幅,並且也很難整理全,因此在此直接提供MSDN的地址。
String Manipulation (CRT)指針
6. Windows API函數對於Windows API函數來講,一般涉及字符串的函數都有A和W兩個版本,好比: CreateFileA和CreateFileW。
這兩個雖然對咱們來講是可引用的,但因爲Windows頭文件的屏蔽,咱們常用CreateFile來進行編程。
而根據第一步的工程屬性切換,代碼中會自動替換成A版本或者W版本。所以對於這點咱們無須操心太多,
惟一須要操心的就是,沒法對應的Unicode的地方,咱們必須採用A版原本處理某些操做,這就須要咱們
顯式指定A版本了,由於工程屬性的關係,CreateFile老是被映射到CreateFileW上。調試
7. 推薦使用 wsprintf對於格式化字符串,這個函數提供了很好的Unicode和MBCS的兼容性。
此函數在Unicode和MBCS下都能正常工做,由於它的兩個參數爲LPTSTR和LPCTSTR。
其次,在MBCS下%s表示MBCS,%S表示Unicode,
在Unicode下%S表示MBCS,%s表示Unicode。
所以,採用這個函數進行字符串格式化的話,基本上是不須要修正就能使用的。
相關的資料請參見MSDN:
wsprintfcode
8. WideCharToMultiByte和MultiByteToWideChar爲了在MBCS和Unicode之間轉換,Windows API提供了這兩個函數。
基本上工程一大,總會遇到不能完全Unicode化的狀況,這個時候就用借用這兩個函數的力量了。
MultiByteToWideChar
WideCharToMultiByte
使用例:
WideCharToMultiByte(CP_OEMCP,NULL,szSrc,-1,szDest,dwLen,NULL,NULL);
※建議:能夠先以dwLen = WideCharToMultiByte(CP_OEMCP,NULL,szSrc,-1,NULL,0,NULL,NULL)
的形式得到szSrc轉換後的字節數(包含/0),而後分配內存後再作字符集轉換。
MultiByteToWideChar(CP_ACP,0,szSrc,-1,szDest,dwLen);
一樣建議先得到szDest所需內存大小(包含/0)後分配內存再作轉換。
9. MBCS專用函數在MBCS的程序中,由於str*系的函數只對應ANSI,對MBCS使用後每每會產生錯誤的結果,
因此每每採用幾個MBCS專用函數來進行糾正。不過因爲這些函數的引入,每每致使Unicode化繁瑣化。
IsDBCSLeadByte和IsDBCSLeadByteEx這兩個函數用來判斷當前字節是否是MBCS的前導字節,
經常在截斷字符串時,不知道截斷點是否是一個雙字節MBCS的正中間的時候使用。
對於Unicode來講,正常的操做永遠不會截到一個雙字節的Unicode字符的正中間,
並且這兩個函數指針對MBCS字符,對於Unicode字符使用的話,後果是沒法估計的。
因此,在Unicode化時,須要把這些函數剔除,而後從新整理處理邏輯。
與此內容相關的幾個資料:
Unicode and Character Set Functions
Character Classification
Strings
10. Unicode非對應函數出於某些緣由,有些函數並無提供Unicode的版本。
若是沒法避免使用到的話,那就須要使用WideCharToMultiByte和MultiByteToWideChar來進行字符集轉換。
這裏列舉幾個已知經常使用函數:
GetProcAddress | 由於DLL的輸出函數名都是ANSI形式保存的,因此沒有提供Unicode版 |
WinSock系列 例: gethostname |
TCP/IP協議誕生比較早,並且只對應ANSI, 因此提供的函數庫天然沒有Unicode版了 |
11. Unicode非對應DLL有時候在程序中調用了第三方的模塊,但許多公司的模塊只提供了MBCS或者ANSI的接口,
對於這種模塊,和前一點同樣,不得不使用WideCharToMultiByte和MultiByteToWideChar來進行字符集轉換。
同時要注意,Unicode化修正代碼的時候,不要盲目把第三方的頭文件一塊兒改掉了。
雖然編譯會經過,可是連接的時候因爲在lib庫中找不到徹底對應的函數聲明,因此最終仍是徒勞。
12. CString&的陷阱在使用MFC的時候,常常會使用CString來保存字符串。MFC中提供的CString並無顯式提供A版本和W版本,
當工程環境是MBCS的時候,CString保存的是LPSTR,而Unicode的時候,CString保存的是LPWSTR。
其實,兩種環境下CString類的結構,大小,甚至代碼都不是徹底同樣的。
若是把CString&做爲一個DLL輸出函數的變量類型來聲明的話,在Unicode化中會碰到一點小麻煩。
固然若是可執行文件和DLL都是MBCS,或者都是Unicode的話沒有任何問題,惟一要保證MFC的版本是同樣就好了。
而若是DLL是MBCS,而可執行文件是Unicode的話,編譯能正常經過,可是程序一跑就會出運行時錯誤。
緣由就是,可執行文件和DLL的CString是對兩種字符集作處理的,兩邊都認爲裏面放着本身能處理的字符集字符串。
對於這種問題,沒有什麼特別好的解決方案,惟一可行的方案就是再作一箇中間層轉換的DLL(MBCS版),
接受可執行文件的Unicode字符串(注意不是CString&),轉換成MBCS的後放入CString中,再繼續調用DLL。
13. 動態調用的陷阱在沒有lib庫文件,只有dll的狀況下,咱們每每會採用動態調用,動態調用的函數聲明咱們會採用typedef來聲明。
可是typedef的掌控權在本身手裏,若是在修正代碼的時候,不當心把char改爲了TCHAR的話,
編譯器是不會拋出任何怨言的。由於在進行動態調用的時候,只要有了函數的入口地址就能被調用,
編譯器只有在靜態調用的時候纔會檢查參數個數和各自的類型,動態調用的時候只管typedef的聲明
是否是和程序中調用的一致,被調用的DLL中函數的實際類型編譯器是管不了,也管不到的。
※代碼二進制化後,函數的聲明信息就被抹除了
14. 指針相減的陷阱兩個指針相減,結果並非兩個指針數值上的差,而是把這個差除以指針指向類型的大小的結果。
好比: WCHAR pA = 0x00400000, pB = 0x00401000, pB - pA的結果是0x1000 / sizeof(WCHAR) = 0x0800
有時候,爲了計算字符串的字節數,會採用這種手段。而後在MBCS編碼時並無刻意去考慮指針相減的問題,
因此得出的結果不會去除以sizeof(TCHAR)。可是到了Unicode,這種編碼顯然就是有問題的,
弄得很差就是內存泄漏。更況且這種錯誤由於不會在編譯階段報錯,因此要發現變得極其困難。
在發現並修正後,惟一能作的也就是吸收教訓,之後編碼的時候多多注意這類問題了
15. 類型char的濫用這個問題涉及面和影響性也是很是龐大的。
在Windows API中,內存指針每每聲明成void*,這個類型表示一個泛型,可以接受其餘全部類型,
也所以不少人習慣性的把內存聲明成char*後傳入。
對於這個看似不嚴重的的錯誤聲明,在Unicode化的過程當中,給編程人員帶來極大的麻煩。
若是這個地址指向字符串,那固然修改爲TCHAR*是正確的,可是若是指向一塊結構體內存,
那麼TCHAR就會把內存擴大成2倍,若是不巧這塊內存體的下方有着重要數據的話,
一旦發生內存覆蓋後,錯誤會隱藏幾分鐘,甚至幾小時幾天後纔會暴露,並且沒法跟蹤。
在VC6.0中,有着BYTE這樣一個宏定義,窺探一下就會發現實際上是unsigned char,雖然和char相差只有一個前綴詞,
可是足以讓維護人員知道這個是表示內存的一個字節,而不是一個ANSI字符。
16. 既存文件的影響文件中的字符和內存中的同樣,基於一種字符集後才能被解釋。
Unicode化的過程當中,咱們修改了代碼,使得運行時數據獲得正確運行的同時,
也必須注意配置文件,數據文件中的字符集是否是也被一併修改掉了。
固然*.ini,註冊表以及數據庫也有這樣的問題,不過由於Windows API,或者數據庫連接提供商
都已經對字符集作了相應的處理,咱們也只須要調用Unicode版本的函數就能迎刃而解。
須要獲得注意只有那些受到你直接操控的數據文件。
17. LPTSTR的泛濫咱們在寫程序的時候,常常處於一種免責心理,別人怎麼寫我就怎麼寫。別人定義字符串用了LPTSTR,
那麼我也用這個必定沒錯!可是呢,程序世界是嚴謹的,聲明若是不恰當的話,一定會引發一些麻煩。
若是你正在寫一段只能處理MBCS的代碼(好比底層第三方的接口只提供了非Unicode版本),
那麼使用LPTSTR來定義就顯得不夠準確,雖然在MBCS下編譯不會存在任何問題,但一旦這個項目
要進行Unicode化的話,LPTSTR就變成了LPWSTR,在接口調用的地方會由於類型不匹配而出現編譯錯誤,
實施修改的程序員就不得不對這個糟糕的定義進行從新調整。但這個不是最可怕的,由於至少編譯器
還可以發現這個錯誤。最可怕的就是那個底層dll的編寫人員也在濫用LPTSTR,那麼編譯器就會被矇騙,
運氣好的話,靜態調用在連接過程當中發現了不匹配,運氣很差的話,動態調用編譯階段不會報任何問題,
等到跑起來就有的你夠受的。因此,若是你不是在爲本身寫代碼,那麼給別人的頭文件請必定要選擇恰當的類型聲明。
18. 其餘在實際Unicode化實施過程當中,由於項目大小,難度不一樣還會遇到一些其餘零零碎碎的問題。
有些多是能夠忽略的,但有些多是難以追蹤而且致命的。
對於有經驗的人可能會在修改過程當中注意到一些問題,可是對於沒有經驗的人,
就要經過調試,查錯,參考資料來一步一步解決問題。
Unicode化能夠說自己就是一種高難度的開發做業,但願經過閱讀本文給那些
即將要實行Unicode化的程序員一些經驗和建議,能在開發過程當中儘早發現問題,解決困難。
最後對能看完本文的讀者說聲謝謝~
附我的看法:
修正難度 | 修正範圍 | 編譯能發現 | |
1. 工程屬性切換 | ★☆☆☆☆ | ★☆☆☆☆ | - |
2. 字符串定義 | ★☆☆☆☆ | ★★★★★ | ○ |
3. 字符串常量定義 | ★☆☆☆☆ | ★★★★★ | ○ |
4. 字符串文字數計算 | ★★☆☆☆ | ★★★☆☆ | × |
5. 字符串函數的替換 | ★★☆☆☆ | ★★★★★ | ○ |
6. Windows API函數 | ☆☆☆☆☆ | ☆☆☆☆☆ | - |
7. 推薦使用 wsprintf | ☆☆☆☆☆ | ☆☆☆☆☆ | - |
8. WideCharToMultiByte和MultiByteToWideChar | ★★★☆☆ | ★★☆☆☆ | ○ |
9. MBCS專用函數 | ★★★★☆ | ★☆☆☆☆ | × |
10. Unicode非對應函數 | ★★☆☆☆ | ★☆☆☆☆ | ○ |
11. Unicode非對應DLL | ★★☆☆☆ | ★★☆☆☆ | ○ |
12. CString&的陷阱 | ★★★★★ | ★☆☆☆☆ | × |
13. 動態調用的陷阱 | ★★★☆☆ | ★★☆☆☆ | × |
14. 指針相減的陷阱 | ★★☆☆☆ | ★★★☆☆ | × |
15. 類型char的濫用 | ★★☆☆☆ | ★★★☆☆ | × |
16. 既存文件的影響 | ★★☆☆☆ | ★☆☆☆☆ | × |
17. LPTSTR的泛濫 | ★★★★★ | ★★★☆☆ | ○ |
18. 其餘 | ★★★★★ | ★★★☆☆ | - |