Windows上的字元轉換之CP_ACP和CP_OEMCP
Windows API函式MultiByteToWideChar用於多位元組編碼字串向寬字串(即UTF-16 LE)的轉碼。它的第一個引數的常用值是CP_ACP和CP_OEMCP。這到底指的是什麼內碼表呢? 我編了小程式做了實驗。
CP_ACP和CP_OEMCP,分別是指當前計算機上的Windows作業系統的Windows內碼表與OEM內碼表。對於東亞的簡體中文、繁體中文、日文、韓文等Win作業系統語言環境,這兩種內碼表是同一個,如簡體中文是內碼表936即GB2312字符集,繁體中文是950即大五碼字符集,韓文是949、日文是932。對於西方國家的拼音文字語言設定,兩個內碼表不同。典型的如English_US,其Windows內碼表是1252、OEM內碼表是437,還有第三個內碼表ISO-8859-1又稱Latin-1或“西歐語言”,是針對英語法語西語德語等西歐語言的擴充套件ASCII字符集。這三者(1252、437、8859-1)都是針對英語但並不相同。
為什麼會有Windows內碼表與OEM內碼表的區別呢?因為在八十年代DOS系統時期,還是“字元終端”的螢幕只能夠顯示的256個字元,這些字元的字形的點陣資訊儲存在硬體的ROM中。DOS作業系統通過系統中斷呼叫驅動程式把這些字形讀出來寫入視訊記憶體。這是由OEM負責字符集中有哪些字元,顯示時為什麼字形的時代,而且一臺PC上只有這麼一套字符集/字形,沒得選,除非你再差一個帶字型檔的“漢卡”。進入了微軟的Windows作業系統時代之後,由於硬體的發展,作業系統有了自己的字形檔案,繪製字元時不再真地去讀ROM,而是用字形檔案(就是字型fonts檔案)來把字元的形狀寫入視訊記憶體。可以選擇用哪種字形:如有襯線的Times
NewRome,還是無襯線的Sans Serif。作業系統預設使用的字符集,就由微軟來定義了,如English_US使用Codepage1252;簡體中文使用Codepage936(即國標2312). 至於那個OEM436,就是legacy,用於向後相容。
綜上,就這麼點事。CP_ACP和CP_OEMCP,分別是UINT的0和1。在WinNls.h中的註釋說明分別是“default to ANSI code page”,“default to OEM code page”。所以,在簡體中文Windows,這兩個巨集表示的都是內碼表936.
下述程式程式碼片段用於測試
UINT codepage=936;
char str[]="我們中國"; //這個char[]必然是多位元組編碼字串
DWORD len;
// 得到我們要轉換的MyString為UNICODE所需要的UNICODE緩衝區的長度
len = MultiByteToWideChar(codepage, 0, str, -1, 0, 0);
wchar_t *buf=new wchar_t[len+10];
MultiByteToWideChar(codepage, 0, str, -1, buf, len);
setlocale(LC_CTYPE,"");//把當前locale字元環境從C/C++預設的"C"設定,改為作業系統的設定(即內碼表936)
wprintf(L"%s",buf); //因為這個C標準庫函式的實現,是把寬字元輸入又轉化為多位元組字元去顯示,所以必須正確設定當前作業系統的多位元組編碼的內碼表
結果:
1. 輸入是char str[]="我們中國"; UINT codepage=936或者54936(這是GB18030內碼表)或者CP_ACP或者CP_OEMCP,都能正確打印出結果“我們中國”。
2. 輸入是char str[]="иい瓣"; UINT codepage=950; 也能正確把上述大五碼字串打印出寬字串輸出結果“我們中國”。
3. 輸入是char str[]="鎴戜滑涓浗 "; UINT codepage=65001; 也能正確把上述UTF-8字串打印出寬字串輸出結果“我們中國”。
附錄:
一個線上GB/BIG5/UTF-8/UNICODE轉碼的網站http://www.dheart.net/bmzh/index.php
ps. 實際上,簡體中文Windows系統的預設內碼表936,不是隻有6763個漢字的GB2316,正確說法是自1995年Windows95起,內碼表936是GBK字符集,包含了20902個漢字。此前,內碼表936與GB2316是一樣的。GB2316 >> GBK >> GB18030 是向後相容的。所以程式設計角度把這三者視作等同,也湊合啦。