前陣子找到將Big5-HKSCS編碼轉為Unicode的解決方案,實際應用卻發現問題 -- 若字串已是Unicode編碼且混雜其他語系字元,HKSCS_Big5ToUnicode41()便無法招架。

延續上回的例子:

在"滙豐銀行 警衞室"後方故意加上"喆"跟"犇"這兩個BIG5編碼不支援的難字,經轉換後,滙與衞轉對了,喆與犇兩個字卻變成"?"。推敲原因是HKSCS_Big5ToUnicode41()將傳入字串視為Big5-HKSCS編碼,在.NET裡加入的喆犇二字則是Unicode編碼,在Big5-HKSCS沒有對應,便成了"?"。

想起hkscs04.dll還有另一個方法 -- HKSCS_PUAToUnicode41(),當初搞不太懂PUA(Private User Area)便多沒理它。這回遇上Unicode難字的困境,反而讓我意會到PUA的意義,一併解開殘留的謎團。當我們用GetEncoding(950).GetString()解讀Big5-HKSCS編碼內容,香港擴充字其實已被成功解析,只是它們被對映成使用者造字區(PUA)的字元,而標準字型檔未提供造字區字型,顯示時便以空白或方框呈現。網友rick提到套用"細明體_HKSCS字型檔"的解法,就是補齊了香港擴充字在造字區編碼對應的字型,讓文字得以正確顯示。然而,在Unicode 4.1標準裡,已收納了這些香港擴充字,不再需要PUA造字區,而HKSCS_PUAToUnicode41()的目的便是將PUA造字區的香港擴充字對應成Unicode 4.1內建的標準字元。

決定改寫HkscsHelper,將HKSCS_Big5ToUnicode41()及HKSCS_PUAToUnicode41()分別包裝成ConvertBig5()及ConvertPua(),並採用網友CIHsieh補充的[MarshalAs(UnmanagedType.LPWStr)]技巧,直接用StringBuilder接收結果,不必扯上Marshal.AllocHGlobal、Marshal.Copy、Marshal.FreeHGlobal,程式清爽許多。

using System;
using System.Runtime.InteropServices;
using System.Text;
 
class HkscsHelper
{
    const uint HKSCS_ERR_INVALID_CHARS = 0x00000001;
 
    [DllImport("hkscs04.dll")]
    public static extern int HKSCS_Big5ToUnicode41(
        uint dwFlags, 
        [In][MarshalAs(UnmanagedType.LPStr)]string lpBig5Str, int cbBig5,
        [Out][MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpUnicode41Str, 
        int cchUnicode41);
 
    [DllImport("hkscs04.dll")]
    public static extern int HKSCS_PUAToUnicode41(
        [In][MarshalAs(UnmanagedType.LPWStr)]string lpPUAStr, int cchPUA,
        [Out][MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpUnicode41Str, 
        int cchUnicode41);
 
    public static string ConvertBig5(string srcString)
    {
        int srcLen = Encoding.GetEncoding(950).GetByteCount(srcString);
        int len = HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS, 
                  srcString, srcLen, null, 0);
        StringBuilder sb = new StringBuilder(len);
        len = HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS,
                  srcString, srcLen, sb, len);
        return sb.ToString().Substring(0, len);
    }
 
    public static string ConvertPua(string srcString)
    {
        int srcLen = srcString.Length;
        int len = HKSCS_PUAToUnicode41(srcString, srcLen, null, 0);
        var sb = new StringBuilder(len);
        len = HKSCS_PUAToUnicode41(srcString, srcLen, sb, len);
        return sb.ToString().Substring(0, len);
    }
}

有了ConvertPua()後,就不怕字串混雜HKSCS及多語系Unicode難字囉~

我不確定二者是否有效能上的差別,由於.NET字串骨子裡都是Unicode編碼,在轉換時,統一使用ConvertPUA()應該就夠了。

感謝rick與CIHsieh的回饋補充,只是起了個頭,靠著大家分享專長與經驗,才漸漸拼湊出完整輪廓揭開謎團,找到更完善的解決方案,這感覺真好! :D


Comments

# by pbnttttt

黑大你好,我在網路上找不到hkscs04.dll,能否請您寄給我這隻dll,謝謝您。 email:pbnttttt@gmail.com

# by vincent

黑大你好,我在網路上找不到hkscs04.dll,能否請您寄給我這隻dll,謝謝您。 請問有沒有新的dll ? fss1189@gmail.com

# by Jeffrey

to pbntttt/vincent, 不好意思,我這邊也沒有留存檔案。

Post a comment