昨天介紹了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
{
    const uint HKSCS_ERR_INVALID_CHARS = 0x00000001;
 
    [DllImport("hkscs04.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern int HKSCS_Big5ToUnicode41(
        uint dwFlags, string lpBig5Str, int cbBig5,
        IntPtr lpUnicode41Str, int cchUnicode41);
 
    public static string 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 = new byte[resLen];
            Marshal.Copy(ipDst, dst, 0, resLen);
            return Encoding.Unicode.GetString(dst);
        }
        finally
        {
            Marshal.FreeHGlobal(ipDst);
        }
    }
}

用前篇文章的"滙豐銀行 警衞室"來驗證看看,成功!!

聲明: 我整合Unmanaged Library的經驗有限,不確定文中採用的做法是否夠嚴謹有效率,尚請高手前輩們不吝指正。


Comments

# by CIHsieh

另一種方式, 供參考: static string Convert(string str) { try { var strLen = Encoding.GetEncoding(950).GetByteCount(str); var sb = new StringBuilder(); var len = (int)HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS, str, strLen, sb, 0); HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS, str, strLen, sb, len); return sb.ToString(); } catch (Exception ex) { Console.WriteLine(ex); return "ERR"; } } public const int HKSCS_ERR_INVALID_CHARS = 1; [DllImport("hkscs04.dll", EntryPoint = "HKSCS_Big5ToUnicode41", CallingConvention = CallingConvention.StdCall)] public static extern uint HKSCS_Big5ToUnicode41( uint dwFlags, [In] [MarshalAs(UnmanagedType.LPStr)] string lpBig5Str, int cbBig5, [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpUnicode41Str, int cchUnicode41);

# by Jeffrey

to CIHsieh, 感謝指點(用力筆記),回歸StringBuilder簡潔多了。

# by rick

試一下包字型,網頁也是會顯示的 細明體_HKSCS

# by Jeffrey

to rick, 好主意! 用HK專屬字型也是種解法,謝謝補充!

# by Rudolf

有把 Unicode 字 對應 Big-5 HKSCS 的方法嗎? Windows 7 倉頡 / 速成 只能打 Unicode 的香港字, 但本人仍在使用 ANSI 的軟件,需要打Big5 的香港字... 又或者有沒有輸入 Unicode 香港字 找到對應 的 Big5 內碼的方法?

# by Jeffrey

to Rudolf, 這個對照表(http://charset.uic.jp/compare/big5-hkscs/chinese-eten/bold/)或許能幫上忙。

# by Victor

想問下重有無save到 hkscs04.dll ? Thanks

Post a comment