前陣子找到將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
{
constuint HKSCS_ERR_INVALID_CHARS = 0x00000001;
[DllImport("hkscs04.dll")]
publicstaticexternint HKSCS_Big5ToUnicode41(
uint dwFlags,
[In][MarshalAs(UnmanagedType.LPStr)]string lpBig5Str, int cbBig5,
[Out][MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpUnicode41Str,
int cchUnicode41);
[DllImport("hkscs04.dll")]
publicstaticexternint HKSCS_PUAToUnicode41(
[In][MarshalAs(UnmanagedType.LPWStr)]string lpPUAStr, int cchPUA,
[Out][MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpUnicode41Str,
int cchUnicode41);
publicstaticstring 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);
}
publicstaticstring 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