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函數中要捨近求遠呢?

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章