先來觀察 Strings.StrConv 方法的簽名:
public static string StrConv(string str, VbStrConv Conversion, [Optional, DefaultParameterValue(0)] int LocaleID)
第三個參數和 MSDN 上的文件有點不同, 上面的簽名是從 Reflector 中摘出來的, 也是這篇文章要記錄的重點, 先來看一些範例:
a1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0404); // a1 = "?樂??" a2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0404); // a2 = "????" b1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0804); // b1 = "書樂う?" b2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0804); // b2 = "书乐う?" c1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0412); // c1 = "?樂う반" c2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0412); // c2 = "??う반" d1 = Strings.StrConv("书樂う반", VbStrConv.TraditionalChinese, 0x0009); // d1 = "書樂う반" d2 = Strings.StrConv("书樂う반", VbStrConv.SimplifiedChinese, 0x0009); // d2 = "书乐う반"
上面 8 個範例的第一個參數摻雜了簡中、繁中、日文和韓文, 第二個參數區分了轉簡體和轉繁體, 第三個參數是 localeID 的部分, 分別包含了 zh-TW (0x0404), zh-CN (0x0840), ko-KR (0x0412), en (0x0009), 讓我們來仔細觀察一下結果, 一切的玄機都在第三個 localeID 參數身上. 我們先假設第三個參數 localeID 是用來表示來源字串的字集, 所以如果這個假設成立的話..., 來看看結果:
- a1: 嗯, 一切如預期的結果, 第一步應該先將 "书樂う반" 轉成符合 zh-TW (0x0404) 的字集, 所以結果是 "?樂??", 然後再根據第二個參數 VbStrConv.TraditionalChinese 結果變成了 "?樂??", 正確!
- a2: 第一步同上, 然後再根據第二個參數 VbStrConv.SimplifiedChinese 結果應該要變成 "?乐??", 可是 a2 的結果卻得到了 "????", 不如預期!
- b1: 第一步應該先將 "书樂う반" 轉成符合 zh-CN 的字集, 所以結果是 "书樂う?", (簡體字集是有包含繁體形態 "樂" 這個字的), 第二個參數 VbStrConv.TraditionalChinese, 所以結果變成 "書樂う?", 正確!
- b2: 正確!
- c1: 韓文字集不太了解, 從結果推測韓文的漢字集如果沒有 "书" 這個字的話, 結果應該算是正確的!
- c2: 從 c1 的結果本來預期應該得到 "?乐う반", 可是結果卻是 "??う반", 不如預期!
- d1: 咦!!! 怎麼會這樣, 完全不如預期, 竟然得到如此漂亮的結果, 本來預期是 4 個 "?" 的!!!
- d2: 一樣得到令人搞不清楚為什麼美麗結果!!!
這到底是怎麼一回事? 是假設錯誤嗎? 可是還有什麼別的可能嗎? 為了解開這個謎團, 於是又祭出了殺手工具 "Reflector", 仔細觀察了 Microsoft.VisualBasic.dll 內的程式碼, 終於了解箇中奧秘!
先來看一下 StrConv 方法反向工程之後的一小部分程式碼(還沒到重點, 所以只節錄最後幾行),
再來追進 vbLCMapString 看一看, 也是看下半部就行了:
橘黃色是和 Encoding 相關的程式碼, 綠色和紅色底線的部分是 Win32 API 用來處理字碼轉換的函式, 綠色底線的函式有一個後綴字 A, 而且輸入的參數是 byte[], 而紅色底線部分的函式則沒有後綴字, 輸入的參數是 string.
看到這兒, 答案已經呼之欲出了, 之所以結果會不如預期都是因為 encoding.GetBytes() 和 encoding.GetString() 這兩個方法給弄砸的, 如果可以跳過它們直接叫用底下畫紅線的 UnsafeNativeMethods.LCMapString 的話, 就不會有那些討厭的問號產生了, 那要怎麼樣才能避過那段我們不想要的程式碼呢? 看一下那個底下有畫虛線的部分 "encoding.IsSingleByte", 嗯! 沒錯, 這就是為什麼 d1, d2 的結果這麼令人驚訝的原因了, 因為 en 的 Encoding 就是 SingleByte 所以會直接跳過 Unicode 和 MBCS 互轉的部分, 而直接進行 Unicode 的轉碼, 於是得到美麗的答案, 整個過程分析完畢!
雖然已經知道整個來龍去脈, 但是如果能再了解一下那個神奇的 Win32API: LCMapString 的話, 想必觀念又可以再更清楚一些. 所以我們再來看看 LCMapString 的重點吧! 嗯~~重點在哪兒咧? 以此篇文章的需求 "簡繁轉換" 來看的話, 只有第二個參數 dwMapFlags 值得我們注意, 打開 MSDN 的文件, 透過索引找到 LCMapString 的章節, 我們可以看到以下的內容,
針對 Windows NT 4.0 以後的作業系統, Microsoft 已經早就幫程式設計師們準備好了一個現成的系統函式來達成簡繁轉換的工作了(唉! 為什麼沒有早點知道!), 看你是要轉簡體 (LCMAP_SIMPLIFIED_CHINESE), 或是轉繁體 (LCMAP_TRADITIONAL_CHINESE), 只要給個參數, 一切就搞定了, 就是這麼簡單!
結論
如果您的需求和我一樣, 只是想把文字內容的簡繁部分轉換, 並不是想轉成 big5 或 gb, 整個輸出入都是 unicode, 而且也不想破壞其他非簡繁文字部分的話, 那麼結論就是照著本篇文章的一開始的 d1, d2 範例呼叫 VB 的 Strings.StrConv 帶上 0x0009 或是其他 SingleByte 字集的 localeID 當成第三個參數就可以啦!!!
如果不想引入 Microsoft.VisualBasic.dll (別問為什麼, 純屬個人偏好) 又想要做到相同的效果, 做法也很簡單, 請參考以下的範例程式碼!!!
public static class ChineseStringUtility { internal const int LOCALE_SYSTEM_DEFAULT = 0x0800; internal const int LCMAP_SIMPLIFIED_CHINESE = 0x02000000; internal const int LCMAP_TRADITIONAL_CHINESE = 0x04000000; [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] internal static extern int LCMapString(int Locale, int dwMapFlags, string lpSrcStr, int cchSrc, [Out] string lpDestStr, int cchDest); public static string ToSimplified(string source) { String target = new String(' ', source.Length); int ret = LCMapString(LOCALE_SYSTEM_DEFAULT, LCMAP_SIMPLIFIED_CHINESE, source, source.Length, target, source.Length); return target; } public static string ToTraditional(string source) { String target = new String(' ', source.Length); int ret = LCMapString(LOCALE_SYSTEM_DEFAULT, LCMAP_TRADITIONAL_CHINESE, source, source.Length, target, source.Length); return target; } }
簡繁體互換
转自:http://charlesbc.blogspot.com/2007/03/wq.html
這個在 dotnet 上,需籍用 Microsort.VisualBasic.dll 的力量了
using Microsoft.VisualBasic;
string str = "中國"; str = Strings.StrConv(str, VbStrConv.SimplifiedChinese, 2052);
MessageBox.Show(str); //簡體的中國
str = Strings.StrConv(str, VbStrConv.TraditionalChinese, 1033);
MessageBox.Show(str); //繁體的中國
請參考 String.StrConv 的 msdn 說明
注意到:
第一個參數是待轉換的字串
第二個參數欲轉換成字體列舉值 (enum)
第三個參數(重要),是文字轉換完後,最後對應的編碼格式 (LocaleID)
1028 繁體中文
1033 ASCII
2052 簡體中文
為何第二次轉換回繁體時,不用 1028 而用 1033 呢?
首先,要先知道,有些簡體中文的編碼值是在繁體中文是對應不到任何字的。此時,顯示的字就會是 "?" 號。
因為 2052 的 「 国] 已經是簡體字了,轉回到繁體時,剛好其編碼對應不到1028 的編碼,因此會是問號。
最好的方法,是以 en-US 做為編碼格式。當以 en-US 做為最後的編碼格式時,又碰到亞洲字時,就會以 unicode 做為儲存編碼格式。此剛好與 dotnet 的 string 儲存格式相同。因此解決了所有問題。
因此,當我們在做簡繁轉換時,請將第三個參數都設成 1033
string str = "中國";
str = Strings.StrConv(str, VbStrConv.SimplifiedChinese, 1033);
Console.WriteLine(str); //簡體的中國
str = Strings.StrConv(str, VbStrConv.TraditionalChinese, 1033);
Console.WriteLine(str); //繁體的中國
str = Strings.StrConv(str, VbStrConv.SimplifiedChinese, 2052);
Console.WriteLine(str); //簡體的中國
str = Strings.StrConv(str, VbStrConv.TraditionalChinese, 2052);
Console.WriteLine(str); //繁體的中國
str = Strings.StrConv(str, VbStrConv.SimplifiedChinese, 1028);
Console.WriteLine(str); //簡體的中?
str = Strings.StrConv(str, VbStrConv.TraditionalChinese, 1028);
Console.WriteLine(str); //繁體的中?