C/C++代碼支持Unicode
這份文檔簡要的說明了如何修改你的C/C++代碼使之支持Unicode。在這里並不准備 解釋太多相關的技術細節並且我得假定你已經基本熟悉Microsoft支持Unicode的方式。 它的主要目的是方便你查詢相關的數據類型和函數,以及修正相應的拼寫錯誤。
My thanks to Yaker Gong for the translation to Chinese. Xie Xie!
使你的C/C++代碼支持Unicode的第一步
- 定義宏 _UNICODE, 如果定義了宏 _MBCS 則取消它的定義(undefine)。
- 在字符串前添加 L 標記或者用 _T宏修飾字符串。
- 使用 Wide 或者 TCHAR 版本的字符串處理函數。
- 確定API中的字符串長度是按字節計數還是按字符個數計數。因為基於字符的顯示和打印(與此不同的是,GUI是基於像素的)使用 列數,而不是字節數或者字符個數。
- 在字符串指針相關的計算中使用GetNext格式,因為一個字符可能包含多於一個Unicode字符單元。
- 注意緩沖區的大小以及防止緩沖區溢出。
改變編碼方式可能需要增大緩沖區或者限制字符串的最大長度。假設單個字符的大小從1個字節變為4個字節,並且字 符串本來20個字符占用20字節,那么你需要將字符串緩沖區擴大為80字節或者將字符串長度限制為5個字符(字符串緩沖區仍為20字節)。注意緩沖區的擴 大可能被限制到一個最大值(比如65KB)。減少字符串長度到一個固定值可能破壞現有的程序,限制字符串長度到固定值可能是危險的。比如,限制到20字 節,將字符串轉化為大寫形式就可能導致字符串變長並且超過限制。 - 將接受或者返回單字符參數的函數替換為使用字符串的版本。 (在一些語言中) 對於單個字符的操作可能導致返回多個代碼點。例如,upper('ß')將 返回"SS"。
- 使用 wmain 代替 main。環境變量也由_environ變 為_wenviron 。
wmain(int argc, wchar_t *argv[], wchar_t *envp[]) - MFC Unicode 程序使用 wWinMain 作為程序入口點(VC++ 6.0)。
project->settings->Link,選擇output選項卡
在Entry point symbol一行加上 wWinMainCRTStartup 。 - 字體的因素,分清使用的字體用於渲染各種語言的字符還是用於腳本中。
文件 I/O, 數據庫, 傳輸協議等因素
- 考慮是否需要讀寫文件、數據庫中的 UTF-8 或者 UTF-16 字符,以及是否進行數據交換。
- 考慮 UTF-16 格式文件中的字節序。
讀寫網絡傳輸的數據總是使用 Big-Endian ,如果你沒有產生 BOM 也使用 Big-Endian。
文件的字節序依賴於文件格式以及/或者源/目標機器的體系結構。
讀取 UTF-16 或者 UTF-32編碼的文件時,考慮是否需要將字符按字節逆序。
對於 streams 和傳輸協議也需要做上述的考慮。 - 傳輸協議和用於數據交換的文件要使用正確的編碼方式。例如 HTTP,HTML,XML 必須設置為 UTF-8 或者 UTF-16。
- 考慮Unicode字節序標記( BOM ,Byte Order Marker) 以及是否需要將它同數據一同寫入。讀取數據時記得去掉BOM。
- 考慮遺留數據和文件的編碼慣例,考慮導入和導出以及傳輸協議。(MultiByteToWideChar, WideCharToMultiByte, mbtowc, wctomb, wctombs, mbstowcs )
- 考慮復制文本到剪貼板
使用 CF_TEXT 格式並且寫入本地編碼的文本(ANSI格式)
使用 CF_UNICODETEXT 格式並寫入Unicode文本 - 數據庫程序需要考慮數據類型 (NCHAR, NVARCHAR) 和模式(schema)的變更,觸發器,存儲過程和查詢,數據的增長,索引以及性能等因素。
注意:Unicode模式的改變對於不同廠商的數據庫產生不同的影響。如果需要數據庫具有較好的可移植性,就要考慮到每種數據庫的特性和行為。
(我知道針對這個問題說得不夠多,以后有機會再補充吧)
流式 I/O
如果你使用Microsoft C++編譯器,你可能遇到與流式 I/O相關的3類問題:
- 不支持Unicode文件名。
解決方式是使用 FILE * _wfopen 函數,之后使用FILE句柄初始化流式 I/O。std::ifstream stm(_wfopen(pFilename, L"r")); - 在 讀/寫 的時候,流式 I/O 會把數據 從本地代碼頁(ANSI格式)轉換到Unicode格式/從Unicode格式轉換到ANSI格式,而非UTF-8 或者 UTF-16。
但是可以修改表示流的類使之支持讀寫 UTF-8 格式字符。你可以自己實現一個讀寫時把數據在 Unicode 和 UTF-8 之間轉換的I/O stream類。codecvt <wchar_t, char_traits <wchar_t> > - 如果要用流式 I/O讀寫 UTF-16 字符,應該用二進制模式打開並且在二進制模式下輸入輸出。可以用如下方法設置 I/O 為二進制模式:
_setmode( _fileno( stdin ), _O_BINARY );
也可以參考 Microsoft 運行時庫參考: "Unicode Stream I/O in Text and Binary Modes".
注意:針對cout/wcout, cin/wcin等並沒有相應的 TCHAR 版本。如果你需要在ANSI/Unicode兩種模式下編譯 代碼,你可能需要自己定義一個名字類似"tout"的宏。
國際化,Unicode高級技術,平台和其它因素
- 考慮使用基於地區的辦法和更進一步的國際化。
- 對於 Windows 95,98 和 Windiws ME,考慮使用 Microsoft MSLU (Microsoft Layer for Unicode)
- 考慮字符串比較和排序,Unicode Collation Algorithm
- 考慮 Unicode Normalization
- 考慮 Character Folding
重新 考慮是否要自己處理這些事情。借助於一家Unicode咨詢公司,然后讓你的團隊集中精力做他們擅長的事情。(嗨, 我們也要謀生啊...)
Unicode字節序標記(BOM) 值
|
Unicode字節序標記(BOM,Byte Order Marker) 是Unicode字符 U+FEFF。(它也能表示一個被稱作 Zero Width No-break Space 的字符)。U+FFFE 這個代碼點在Unicode中是非法的,它永遠不應該出現在一個Unicode字符流中。所以BOM可以作為放置於文件(或者一個字符串)的起始作為字節 序的指示器。對UTF-16編碼而言,如果第一個字符的值是FE FF 那么文本和讀取文本的機器有相同的字節序。如果是 FF FE,那么有相反的字節序並且需要對每 個16-bit字按字節逆序。同樣的,BOM指示了UTF-32編碼的文本的字節序。 注意不是所有的文件都以Unicode字節序標記開始。事實上,Unicode標准稱若不以Unicode字節序標記 (BOM)開始(數據)就必須被表示成big-endian形式。 字符 U+FEFF 同樣作為不同Unicode編碼方式的標記。左邊的表格說明了 U+FEFF 在每一種Unicode編碼方式中的值。注意:按照定義,標記為UTF-16BE, UTF-32BE, UTF-32LE or UTF-16LE 的文本不應該有BOM,字節序已經由標記本身指出了。 對於使用 SCSU (Standard Compression Scheme for Unicode) 算法壓縮過的文本,也有一個推薦的簽名。 |
常量和全局變量
| ANSI版本 | 寬字符版本 | 宏定義版本(TCHAR) |
|---|---|---|
| EOF | WEOF | _TEOF |
| _environ | _wenviron | _tenviron |
| _pgmptr | _wpgmptr | _tpgmptr |
數據類型
| ANSI版本 | 寬字符版本 | 宏定義版本(TCHAR) |
|---|---|---|
| char | wchar_t | _TCHAR |
| _finddata_t | _wfinddata_t | _tfinddata_t |
| __finddata64_t | __wfinddata64_t | _tfinddata64_t |
| _finddatai64_t | _wfinddatai64_t | _tfinddatai64_t |
| int | wint_t | _TINT |
| signed char | wchar_t | _TSCHAR |
| unsigned char | wchar_t | _TUCHAR |
| char | wchar_t | _TXCHAR |
| L | _T 或者 _TEXT | |
| LPSTR (char *) |
LPWSTR (wchar_t *) |
LPTSTR (_TCHAR *) |
| LPCSTR (const char *) |
LPCWSTR (const wchar_t *) |
LPCTSTR (const _TCHAR *) |
| LPOLESTR (For OLE) |
LPWSTR | LPTSTR |
Platform SDK字符串處理API
有很多Windows API函數會根據宏 UNICODE 是 否被定義而編譯成不同形式。那些需要同時操作ANSI字符和寬字符的模塊需要了解這一點。否則,應該使用宏定義版本的名字,這樣的話就只需要定義宏 UNICODE 並且重新編譯程序。
下列列表並沒有列舉所有的有ANSI和寬字符兩個版本的API,只列舉了與字符和字符串處理相關的一些。如果需要查看與代碼頁和地區相 關 的API請查看WinNLS.h頭文件。
| ANSI版本 | 寬字符版本 | 宏定義版本(TCHAR) |
|---|---|---|
| CharLowerA | CharLowerW | CharLower |
| CharLowerBuffA | CharLowerBuffW | CharLowerBuff |
| CharNextA | CharNextW | CharNext |
| CharNextExA | CharNextExW | CharNextEx |
| CharPrevA | CharPrevW | CharPrev |
| CharPrevExA | CharPrevExW | CharPrevEx |
| CharToOemA | CharToOemW | CharToOem |
| CharToOemBuffA | CharToOemBuffW | CharToOemBuff |
| CharUpperA | CharUpperW | CharUpper |
| CharUpperBuffA | CharUpperBuffW | CharUpperBuff |
| CompareStringA | CompareStringW | CompareString |
| FoldStringA | FoldStringW | FoldString |
| GetStringTypeA | GetStringTypeW | GetStringType |
| GetStringTypeExA | GetStringTypeExW | GetStringTypeEx |
| IsCharAlphaA | IsCharAlphaW | IsCharAlpha |
| IsCharAlphaNumericA | IsCharAlphaNumericW | IsCharAlphaNumeric |
| IsCharLowerA | IsCharLowerW | IsCharLower |
| IsCharUpperA | IsCharUpperW | IsCharUpper |
| LoadStringA | LoadStringW | LoadString |
| lstrcatA | lstrcatW | lstrcat |
| lstrcmpA | lstrcmpW | lstrcmp |
| lstrcmpiA | lstrcmpiW | lstrcmpi |
| lstrcpyA | lstrcpyW | lstrcpy |
| lstrcpynA | lstrcpynW | lstrcpyn |
| lstrlenA | lstrlenW | lstrlen |
| OemToCharA | OemToCharW | OemToChar |
| OemToCharBuffA | OemToCharBuffW | OemToCharBuff |
| wsprintfA | wsprintfW | wsprintf |
| wvsprintfA | wvsprintfW | wvsprintf |
CRT字符串處理API
函數按照ANSI版本的ASCII字母順序排序,方便轉換到相應的Unicode版本。
| ANSI版本 | 寬字符版本 | 宏定義版本(TCHAR) |
|---|---|---|
| _access | _waccess | _taccess |
| _atoi64 | _wtoi64 | _tstoi64 |
| _atoi64 | _wtoi64 | _ttoi64 |
| _cgets | _cgetws | cgetts |
| _chdir | _wchdir | _tchdir |
| _chmod | _wchmod | _tchmod |
| _cprintf | _cwprintf | _tcprintf |
| _cputs | _cputws | _cputts |
| _creat | _wcreat | _tcreat |
| _cscanf | _cwscanf | _tcscanf |
| _ctime64 | _wctime64 | _tctime64 |
| _execl | _wexecl | _texecl |
| _execle | _wexecle | _texecle |
| _execlp | _wexeclp | _texeclp |
| _execlpe | _wexeclpe | _texeclpe |
| _execv | _wexecv | _texecv |
| _execve | _wexecve | _texecve |
| _execvp | _wexecvp | _texecvp |
| _execvpe | _wexecvpe | _texecvpe |
| _fdopen | _wfdopen | _tfdopen |
| _fgetchar | _fgetwchar | _fgettchar |
| _findfirst | _wfindfirst | _tfindfirst |
| _findnext64 | _wfindnext64 | _tfindnext64 |
| _findnext | _wfindnext | _tfindnext |
| _findnexti64 | _wfindnexti64 | _tfindnexti64 |
| _fputchar | _fputwchar | _fputtchar |
| _fsopen | _wfsopen | _tfsopen |
| _fullpath | _wfullpath | _tfullpath |
| _getch | _getwch | _gettch |
| _getche | _getwche | _gettche |
| _getcwd | _wgetcwd | _tgetcwd |
| _getdcwd | _wgetdcwd | _tgetdcwd |
| _ltoa | _ltow | _ltot |
| _makepath | _wmakepath | _tmakepath |
| _mkdir | _wmkdir | _tmkdir |
| _mktemp | _wmktemp | _tmktemp |
| _open | _wopen | _topen |
| _popen | _wpopen | _tpopen |
| _putch | _putwch | _puttch |
| _putenv | _wputenv | _tputenv |
| _rmdir | _wrmdir | _trmdir |
| _scprintf | _scwprintf | _sctprintf |
| _searchenv | _wsearchenv | _tsearchenv |
| _snprintf | _snwprintf | _sntprintf |
| _snscanf | _snwscanf | _sntscanf |
| _sopen | _wsopen | _tsopen |
| _spawnl | _wspawnl | _tspawnl |
| _spawnle | _wspawnle | _tspawnle |
| _spawnlp | _wspawnlp | _tspawnlp |
| _spawnlpe | _wspawnlpe | _tspawnlpe |
| _spawnv | _wspawnv | _tspawnv |
| _spawnve | _wspawnve | _tspawnve |
| _spawnvp | _wspawnvp | _tspawnvp |
| _spawnvpe | _wspawnvpe | _tspawnvpe |
| _splitpath | _wsplitpath | _tsplitpath |
| _stat64 | _wstat64 | _tstat64 |
| _stat | _wstat | _tstat |
| _stati64 | _wstati64 | _tstati64 |
| _strdate | _wstrdate | _tstrdate |
| _strdec | _wcsdec | _tcsdec |
| _strdup | _wcsdup | _tcsdup |
| _stricmp | _wcsicmp | _tcsicmp |
| _stricoll | _wcsicoll | _tcsicoll |
| _strinc | _wcsinc | _tcsinc |
| _strlwr | _wcslwr | _tcslwr |
| _strncnt | _wcsncnt | _tcsnbcnt |
| _strncnt | _wcsncnt | _tcsnccnt |
| _strncnt | _wcsncnt | _tcsnccnt |
| _strncoll | _wcsncoll | _tcsnccoll |
| _strnextc | _wcsnextc | _tcsnextc |
| _strnicmp | _wcsnicmp | _tcsncicmp |
| _strnicmp | _wcsnicmp | _tcsnicmp |
| _strnicoll | _wcsnicoll | _tcsncicoll |
| _strnicoll | _wcsnicoll | _tcsnicoll |
| _strninc | _wcsninc | _tcsninc |
| _strnset | _wcsnset | _tcsncset |
| _strnset | _wcsnset | _tcsnset |
| _strrev | _wcsrev | _tcsrev |
| _strset | _wcsset | _tcsset |
| _strspnp | _wcsspnp | _tcsspnp |
| _strtime | _wstrtime | _tstrtime |
| _strtoi64 | _wcstoi64 | _tcstoi64 |
| _strtoui64 | _wcstoui64 | _tcstoui64 |
| _strupr | _wcsupr | _tcsupr |
| _tempnam | _wtempnam | _ttempnam |
| _ui64toa | _ui64tow | _ui64tot |
| _ultoa | _ultow | _ultot |
| _ungetch | _ungetwch | _ungettch |
| _unlink | _wunlink | _tunlink |
| _utime64 | _wutime64 | _tutime64 |
| _utime | _wutime | _tutime |
| _vscprintf | _vscwprintf | _vsctprintf |
| _vsnprintf | _vsnwprintf | _vsntprintf |
| asctime | _wasctime | _tasctime |
| atof | _wtof | _tstof |
| atoi | _wtoi | _tstoi |
| atoi | _wtoi | _ttoi |
| atol | _wtol | _tstol |
| atol | _wtol | _ttol |
| 字符比較 | 映射為宏或者inline 函數 | _tccmp |
| 字符拷貝 | 映射為宏或者inline 函數 | _tccpy |
| 字符長度 | 映射為宏或者inline 函數 | _tclen |
| ctime | _wctime | _tctime |
| fgetc | fgetwc | _fgettc |
| fgets | fgetws | _fgetts |
| fopen | _wfopen | _tfopen |
| fprintf | fwprintf | _ftprintf |
| fputc | fputwc | _fputtc |
| fputs | fputws | _fputts |
| freopen | _wfreopen | _tfreopen |
| fscanf | fwscanf | _ftscanf |
| getc | getwc | _gettc |
| getchar | getwchar | _gettchar |
| getenv | _wgetenv | _tgetenv |
| gets | getws | _getts |
| isalnum | iswalnum | _istalnum |
| isalpha | iswalpha | _istalpha |
| isascii | iswascii | _istascii |
| iscntrl | iswcntrl | _istcntrl |
| isdigit | iswdigit | _istdigit |
| isgraph | iswgraph | _istgraph |
| islead (總是返回FALSE) | (總是返回FALSE) | _istlead |
| isleadbyte (總是返回FALSE) | isleadbyte (總是返回FALSE) | _istleadbyte |
| islegal (總是返回TRUE) | (總是返回TRUE) | _istlegal |
| islower | iswlower | _istlower |
| isprint | iswprint | _istprint |
| ispunct | iswpunct | _istpunct |
| isspace | iswspace | _istspace |
| isupper | iswupper | _istupper |
| isxdigit | iswxdigit | _istxdigit |
| main | wmain | _tmain |
| perror | _wperror | _tperror |
| printf | wprintf | _tprintf |
| putc | putwc | _puttc |
| putchar | putwchar | _puttchar |
| puts | _putws | _putts |
| remove | _wremove | _tremove |
| rename | _wrename | _trename |
| scanf | wscanf | _tscanf |
| setlocale | _wsetlocale | _tsetlocale |
| sprintf | swprintf | _stprintf |
| sscanf | swscanf | _stscanf |
| strcat | wcscat | _tcscat |
| strchr | wcschr | _tcschr |
| strcmp | wcscmp | _tcscmp |
| strcoll | wcscoll | _tcscoll |
| strcpy | wcscpy | _tcscpy |
| strcspn | wcscspn | _tcscspn |
| strerror | _wcserror | _tcserror |
| strftime | wcsftime | _tcsftime |
| strlen | wcslen | _tcsclen |
| strlen | wcslen | _tcslen |
| strncat | wcsncat | _tcsncat |
| strncat | wcsncat | _tcsnccat |
| strncmp | wcsncmp | _tcsnccmp |
| strncmp | wcsncmp | _tcsncmp |
| strncpy | wcsncpy | _tcsnccpy |
| strncpy | wcsncpy | _tcsncpy |
| strpbrk | wcspbrk | _tcspbrk |
| strrchr | wcsrchr | _tcsrchr |
| strspn | wcsspn | _tcsspn |
| strstr | wcsstr | _tcsstr |
| strtod | wcstod | _tcstod |
| strtok | wcstok | _tcstok |
| strtol | wcstol | _tcstol |
| strtoul | wcstoul | _tcstoul |
| strxfrm | wcsxfrm | _tcsxfrm |
| system | _wsystem | _tsystem |
| tmpnam | _wtmpnam | _ttmpnam |
| tolower | towlower | _totlower |
| toupper | towupper | _totupper |
| ungetc | ungetwc | _ungettc |
| vfprintf | vfwprintf | _vftprintf |
| vprintf | vwprintf | _vtprintf |
| vsprintf | vswprintf | _vstprintf |
| WinMain | wWinMain | _tWinMain |
