1. 程式人生 > >GBK(GB2312)向UTF-8的編碼轉換

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的MultiByteToWideCharWideCharToMultiByte兩個函式,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函式中要捨近求遠呢?