昨天介紹了Big5-HKSCS,初步心得是: Big5-HKSCS跟Big5一樣是歷史的眼淚,新一代Unicode標準已能涵蓋其字元範圍又能同時兼容各國語系。因此,拋棄ANSI規格,回歸Unicode才是王道!!
但這衍生一個需求 -- 若既存文字檔或其他老系統仍採有Big5-HKSCS編碼內容,是否能用.NET程式轉換為Unicode呢?
拿這種問題來問傳說中偉大的中文編碼解碼工具程式作者,是一種羞辱吧? 只見那個其貌不揚的中年男子,右手收在腰後,不屑地伸出左手在鍵盤上飛快地敲了一段程式:
File.ReadAllText("d:\\hkscs-test.htm", Encoding.GetEncoding(951));
接著用鄙夷的口吻說道: "Encoding.GetEncoding(950)解析Big5的寫法都示範過幾百遍,Big5-HKSCS的CodePage是951,照著用Encdoing.GetEncoding(951)不就好了? 這也要問? 笨!!"
實測程式。哐噹! 不知哪裡飛來一塊鐵板,砸在剛才還盛氣凌人的中年男子頭上...
.NET掌了一個NotSupportedException: {"No data is available for encoding 951."} !!
查了MSDN討論區文章,發現一件殘酷的事實 -- Vista之後,Windows已經不再提供HKSCS編碼語言包了!
Starting with Windows Vista, HKSCS-2004 characters are only be supported as Unicode 4.1 or later. All characters are assigned standard, non-PUA codepoints. The characters are displayed with the MingLiU font, and these characters can be entered via the keyboard. The patch that provides Big5 encoding of HKSCS is unsupported in Windows Vista and later. A utility provided by Microsoft is available to convert HKSCS and Unicode PUA-encoded characters to Unicode 4.1 version. 951版本中的Big5編碼字在Vista之後的版本是不兼容的。在Windows XP之前的版本,你可以安裝語言包支持它。
所幸,文中提到微軟有個轉換工具能將HKSCS轉成Unicode。找到一個程式庫 -- Microsoft Character Code Conversion Routines For HKSCS-2004,它是個Unmanaged Library(hkscs04.dll),只有C++範例,而我只會寫C#,得透過DllImport外部函式庫的方式呼叫它。
可能因為涉及字串內容語系轉換,我發現先前在其他PInvoke函式用StringBuilder接收結果字串的技巧不管用,只好胡亂試些繞道做法。最後,C/C++麻瓜花了點時間試出用IntPtr當成結果字串參數、用Marshal.AllocHGlobal()取得記憶體空間、再用Marshal.Copy()將內容搬至byte[]的做法,總算順利取回轉換結果。最後,再把邏輯包成string Convert(string) .NET方法方便使用。
using System;
using System.Runtime.InteropServices;
using System.Text;
class HkscsHelper
{
constuint HKSCS_ERR_INVALID_CHARS = 0x00000001;
[DllImport("hkscs04.dll", CharSet = CharSet.Ansi, SetLastError = true)]
publicstaticexternint HKSCS_Big5ToUnicode41(
uint dwFlags, string lpBig5Str, int cbBig5,
IntPtr lpUnicode41Str, int cchUnicode41);
publicstaticstring Convert(string srcString)
{
int srcLen = Encoding.GetEncoding(950).GetByteCount(srcString);
int len = HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS,
srcString, srcLen, IntPtr.Zero, 0);
IntPtr ipDst = Marshal.AllocHGlobal(len);
try
{
int resLen = 2 * HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS,
srcString, srcLen, ipDst, len);
byte[] dst = newbyte[resLen];
Marshal.Copy(ipDst, dst, 0, resLen);
return Encoding.Unicode.GetString(dst);
}
finally
{
Marshal.FreeHGlobal(ipDst);
}
}
}
用前篇文章的"滙豐銀行 警衞室"來驗證看看,成功!!
聲明: 我整合Unmanaged Library的經驗有限,不確定文中採用的做法是否夠嚴謹有效率,尚請高手前輩們不吝指正。