天天看點

GBK(GB2312)向UTF-8的編碼轉換

最近做一個IE插件,要從網頁中取得文字,編碼到一個URL中去。在前一篇文章“中文URL編碼”中,粗略地介紹了URL編碼的規則,以及中文URL編碼的過程,但在如何将GBK或者GB2312編碼的漢字轉換到UTF-8編碼仍然是一個問題。編碼是一個很複雜的問題,我也了解甚少,這裡隻是寫寫我的經驗,歡迎補充和指正。

在PHP、.NET中,編碼的轉換都比較容易。ATL中有一些宏是用來做編碼轉換的,我沒試過,而且我更願意用後面所講的方法。

在COM程式設計中,字元串多存儲在BSTR結構中。網上許多文章都說這個資料結構中存儲的就是Unicode,我就試了好多次從Unicode轉UTF-8,未遂。在Debug的時候,含有中文字元串的BSTR能夠正常顯示,說明它的編碼應該是GBK.

如何從GBK轉換到UTF-8呢?libiconv應該可以做到,然而我使用它的Windows port後,可以編譯、注冊COM元件,就是工具欄出不來了,于是放棄。上網搜尋,得到一個被廣泛轉載的CChineseCode類。然而它僅僅針對漢字(每個漢字在UTF-8編碼中占3個位元組),如果字元串中有英文,就有麻煩了,因為英文在UTF-8編碼中隻有一個位元組。另外有的字元會占用更多的位元組。是以這個類并不适用。

正确的方法是用Win32 API的MultiByteToWideChar和WideCharToMultiByte兩個函數,Wide character指的就是Unicode. GBK和UTF-8之間的轉換,需要用Unicode作為橋梁(在這種方法裡)。比如我們要轉換這樣一個字元串”編碼 - Google 搜尋”。

從GBK向Unicode轉換

該字元串在BSTR類型的變量in中存儲,首先将其轉換為普通的字元串:

char *lpszText = _com_util::ConvertBSTRToString(in);
      

此時,如果用strlen函數取得lpszText的長度,則為18,4個漢字,每個占兩個位元組,另外有10個英文字元。是以說GBK/GB2312是MultiByte而不是WideChar. 并且有lpszText[0] == 0xb1 && lpszText[1] == 0xe0,在微軟Windows Codepage 936這一頁上查到果然是“編”字,更堅定了我們認為它是GBK的信心。

轉換到Unicode所用的函數是MultiByteToWideChar,第一個參數是MultiByte的Code page,如果确定是GBK,就可以使用936. 我考慮它應該是與系統有關的(比如日語系統上應該是932),是以使用CP_ACP,系統所用的Codepage.

先通過将cchWideChar參數設定為0,取得轉換後需要的空間大小,然後配置設定空間,再做實際的轉換(轉換時cbMultiByte為-1表示要轉換的字元串以0結尾)。代碼如下:

int wLen = MultiByteToWideChar(CP_ACP, 0, lpszText, -1, NULL, 0);
LPWSTR wStr = (LPWSTR)CoTaskMemAlloc(wLen * sizeof(WCHAR));
MultiByteToWideChar(CP_ACP, 0, lpszText, -1, wStr, wLen);
      

wLen是15,注意是指寬字元的個數,很貼心,14個字元,加上末尾的結束符。配置設定空間的時候也要注意,不是15個位元組,而應該配置設定30個位元組。這些在MSDN中都有說明,仔細看cchWideChar參數的介紹。最後一行代碼執行後,wStr中就是這些漢字的Unicode了,檢視一下,wStr[0] == 0×7f16,剛才在微軟Windows Codepage 936查找時,“編”字的下面标明7f16,就是它的Unicode編碼,說明一切正常。

從Unicode向UTF-8轉換

轉換到Unicode後,就可以使用WideCharToMultiByte函數将其轉換到UTF-8編碼,這次的code page要用CP_UTF8. 和前面的轉換一樣,先計算所需要的空間大小并配置設定,再做實際轉換。

int aLen = WideCharToMultiByte(CP_UTF8, 0, wStr, -1, NULL, 0, NULL, NULL);
char* converted = (char*)CoTaskMemAlloc(aLen);
WideCharToMultiByte(CP_UTF8, 0, wStr, -1, converted, aLen, NULL, NULL);
      

aLen為23,因為4個漢字,每個占3個位元組,加上10個英文字元(每個占1位元組),再加末尾的’/0′,正好是23. 現在converted裡就是字元串”編碼 - Google 搜尋”的UTF-8編碼。converted[0] == 0xe7 && converted[1] == 0xbc,正是“編”字的UTF-8編碼。

好了,現在終于得到了中英文混合字元串的UTF-8位元組序列,可以進行URL編碼(percent encoding)了。

如果你也看了CChineseCode類的代碼,就會奇怪既然作者知道用WideCharToMultiByte做GB2312到Unicode的轉換,為什麼在UnicodeToUTF_8函數中要舍近求遠呢?

繼續閱讀