国际化困境(第二篇)

来源:互联网 发布:淘宝女式薄绒内衣套装 编辑:程序博客网 时间:2024/06/01 10:49

难得我写系列文章,我的作风更偏向于一鼓作气。(那接下去怎么说?再而衰,三而竭……希望不是这样,呵呵……)
七、OEM与ANSI的转换
好,接上次,上次讲到OEM和ANSI,在文章后面我还给出一张所谓“全图”,当然,只针对两个code page的0x80到0xFF的字符,一个是437,可以认为是英文版的OEM code page,一个是1252,可认为是英文版的ANSI code page,我还说了,一般情况下,Windows环境下是不需要显示OEM字符的,但有些文章是早在DOS时代就已经写好了,现在要拿到Windows下来显示,但由于编码不一样,那显示出来的东西就有可能不对了啊。在以前DOS环境下,我们经常能看到类似下图的这种“画面”,一些框框点点,当然,现在我没有DOS了,就用控制台来代替,不过之前要先切换code page到437,表示使用IBM PC的默认编码,然后选中字体为Licida Console。

DOS's display

这个rectangle.txt可以用下面这段代码生成:

[cpp] view plaincopy
  1. {  
  2.     BYTE byChars[] = {0xC9, 0XCD, 0xCD, 0xCD, 0xCD, 0xCD, 0XCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0XCD, 0xCD, 0xCD, 0xCD, 0xCD, 0XCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0XCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xBB, 0x0D, 0x0A, 0xBA, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x44, 0x4f, 0x53, 0x27, 0x73, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xba, 0x0d, 0x0a, 0xc8, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xbc, 0x0d, 0x0a};  
  3.     HANDLE hFile = CreateFile(TEXT("rectangle.txt"), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);     
  4.     DWORD dwWritten;     
  5.     WriteFile(hFile, byChars, sizeof(byChars), &dwWritten, NULL);  
  6.     CloseHandle(hFile);     
  7. }  

那我们在Windows环境下,直接打开rectangle.txt文件查看,会怎么样呢?我这里有个截图:

OEM_diplay_directly_in_window

要有这种显示,得按照上一篇文章说的那样,在控制面板里的“区域和语言选项”里,把“非Unicode程序使用的语言”设置为英语(美国),否则显示效果可能会不同。为什么会这样呢?原因就是Windows使用的编码和DOS使用的编码不一样,这在上一篇已经提起过这个问题,并且在最后给出了一个图,大家对照这个图看,就知道为什么DOS下的点点框框,到了Windows下就变成了别的字符。那问题来了,像rectangle.txt这种文件是早就写好的DOS文件,我不想去改它啊,如果内容多,改起来多麻烦,但我现在用的是Windows啊,我能不能有别的办法让它“正常点”显示?办法是有的,请看下面的代码,其中m_edit绑定到一个Edit控件。

[cpp] view plaincopy
  1. {  
  2.     HANDLE hFile = CreateFile(TEXT("rectangle.txt"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);     
  3.     DWORD dwSize = GetFileSize(hFile,NULL);  
  4.     BYTE *pContent = new BYTE[dwSize+1];  
  5.     DWORD dwRead;  
  6.     ReadFile(hFile, pContent, dwSize, &dwRead, NULL);  
  7.     CloseHandle(hFile);  
  8.     OemToChar((const char *)pContent, (char *)pContent);  
  9.     ::SetWindowTextA(m_edit.m_hWnd, (CHAR *)pContent);  
  10.     delete[] pContent;  
  11. }  
  

这里同样,需要修改系统设置,使用英语(美国),运行,下图是我的结果:

 converted oem display in window

这样看起来就比较“整齐”了吧,如果你有兴趣想看看OemToChar这个函数是怎么转换的,可以把asciiex.txt的转换前后内码对比一下,代码我就不贴了,下图是我整理出来的图,供参考。

OEM to ANSI table

上图画得我好累,给点掌声吧……浅绿色横条是OEM字符,蓝色数字是它的内码,无浅绿条的符号是对应的ANSI字符,红色数字是ANSI的内码,大家体会体会这个转换是怎么一回事。
八、Locale
Locale中文直译是“场所”,但我想在这里叫做“语言环境”更合适一些,不同的Locale对字符串的表达就不太一样,比如说日期,中国人的写法是“2009-3-23”,美国人的写法是“Mar. 23, 2009”,英国人的写法是“23 March 2009”,这都是不一样的,除了日期外,还包括一些诸如货币,时间,数字的表达,各国也是不尽相同,或者说不同的语言环境下不同吧,那么,给这些所谓语言环境安排一个ID,来方便我们管理这些表达上的差距,这个ID就叫做Locale ID,简写为LCID,如何知道当前所使用的LCID?

[cpp] view plaincopy
  1. LCID lcidSys = GetSystemDefaultLCID();  
  2. LCID lcidUser = GetUserDefaultLCID();  
  3. LCID lcidThread = GetThreadLocale();  
  4. TRACE(TEXT("System Default LCID: %d/nUser Default LCID: %d/nThread Locale: %d/n"), lcidSys, lcidUser, lcidThread);  

有时候,你会发现这几个LCID不同,比如我打印出来的情况是:
System Default LCID: 1033
User Default LCID: 2052
Thread Locale: 2052
那么,这1033,2052到底代表什么呢?
我们认为Locale是一个跟语言有关的东西,所以LCID应该跟语言相对应,那全世界的语言很多啊,光是英语就有好多种,美国英语,英国英语,澳大利亚英语,加拿大英语……它们都是英语,应该说大部分都相同,但不同之处也是显而易见的,前面举的那个日期表达的例子,就可以看出来,英国人的习惯和美国人的习惯是不太一样,所以微软把语言分为两个部分,一部分叫“Primary Language ID”,另一部分叫“Sublanguage ID”,两者合一起才是Language ID,Language ID再外加一个Sort ID,就成为LCID,Sort ID是什么?就是排序方法ID了,像英文这种字母文字,是无所谓排序方法的,正序就a-z,逆序就z-a,但中文就不同了,哪个前,哪个后?我至少知道两种排序方法,一种是按照笔画,一种是按照拼音,另外还有日文,韩文等,也需要Sort ID。
那一共有多少种Primary Language ID和Sublanguage ID呢?他们值又是多少?MSDN上是有说明的,输入“MAKELANGID”查找一下即可,东西还比较多,我就不一一列了。比如美国英语,我们可以在Primary Language这张表中查到English的ID是0x09,然后在Sublanguage表中查到US English的sublanguage ID为0x01,MAKELANGID(0x09, 0x01),结果是0x409。然后再用MAKELCID(0x409, SORT_DEFAULT)生成LCID,由于英文无所谓排序,所以LCID正好就是Language ID。再比如我们再熟悉不过的中国大陆简体中文,中文,Primary ID是0x04,简体中文Sublanguage ID是0x02,MAKELANGID(0x04, 0x02),结果是0x804,再MAKELCID(0x804, SORT_CHINESE_PRCP),得出的结果是0x804,即2052,由于SORT_CHINESE_PRCP(按拼音排序)正好是0,所以LCID就是Language ID。
了解了Locale,我们接下去就要了解如何使用Locale了,前面说了,想日期格式,时间格式,货币格式,这种东西跟Locale是很有关系的,我们如何来获取这些东西的详情?有一个API可以帮我们忙——GetLocaleInfo。

下面的程序就是利用GetLocaleInfo来获取各种跟语言环境相关的显示格式。

[cpp] view plaincopy
  1. struct StruLocaleInfo  
  2. {  
  3.     DWORD dwValue;  
  4.     TCHAR *lpStrName;  
  5. } g_localeAll[] =   
  6. {  
  7. LOCALE_ICALENDARTYPE,               TEXT("LOCALE_ICALENDARTYPE"),  
  8. LOCALE_ICALENDARTYPE,               TEXT("LOCALE_ICALENDARTYPE"),  
  9. LOCALE_ICENTURY,                    TEXT("LOCALE_ICENTURY"),  
  10. LOCALE_ICOUNTRY,                    TEXT("LOCALE_ICOUNTRY"),  
  11. LOCALE_ICURRDIGITS,                 TEXT("LOCALE_ICURRDIGITS"),  
  12. LOCALE_ICURRENCY,                   TEXT("LOCALE_ICURRENCY"),  
  13. LOCALE_IDATE,                       TEXT("LOCALE_IDATE"),  
  14. LOCALE_IDAYLZERO,                   TEXT("LOCALE_IDAYLZERO"),  
  15. LOCALE_IDEFAULTANSICODEPAGE,        TEXT("LOCALE_IDEFAULTANSICODEPAGE"),  
  16. LOCALE_IDEFAULTCODEPAGE,            TEXT("LOCALE_IDEFAULTCODEPAGE"),  
  17. LOCALE_IDEFAULTCOUNTRY,             TEXT("LOCALE_IDEFAULTCOUNTRY"),  
  18. LOCALE_IDEFAULTEBCDICCODEPAGE,      TEXT("LOCALE_IDEFAULTEBCDICCODEPAGE"),  
  19. LOCALE_IDEFAULTLANGUAGE,            TEXT("LOCALE_IDEFAULTLANGUAGE"),  
  20. LOCALE_IDEFAULTMACCODEPAGE,         TEXT("LOCALE_IDEFAULTMACCODEPAGE"),  
  21. LOCALE_IDIGITS,                     TEXT("LOCALE_IDIGITS"),  
  22. LOCALE_IDIGITSUBSTITUTION,          TEXT("LOCALE_IDIGITSUBSTITUTION"),  
  23. LOCALE_IFIRSTDAYOFWEEK,             TEXT("LOCALE_IFIRSTDAYOFWEEK"),  
  24. LOCALE_IFIRSTWEEKOFYEAR,            TEXT("LOCALE_IFIRSTWEEKOFYEAR"),  
  25. LOCALE_IINTLCURRDIGITS,             TEXT("LOCALE_IINTLCURRDIGITS"),  
  26. LOCALE_ILANGUAGE,                   TEXT("LOCALE_ILANGUAGE"),  
  27. LOCALE_ILDATE,                      TEXT("LOCALE_ILDATE"),  
  28. LOCALE_ILZERO,                      TEXT("LOCALE_ILZERO"),  
  29. LOCALE_IMEASURE,                    TEXT("LOCALE_IMEASURE"),  
  30. LOCALE_IMONLZERO,                   TEXT("LOCALE_IMONLZERO"),  
  31. LOCALE_INEGCURR,                    TEXT("LOCALE_INEGCURR"),  
  32. LOCALE_INEGNUMBER,                  TEXT("LOCALE_INEGNUMBER"),  
  33. LOCALE_INEGSEPBYSPACE,              TEXT("LOCALE_INEGSEPBYSPACE"),  
  34. LOCALE_INEGSIGNPOSN,                TEXT("LOCALE_INEGSIGNPOSN"),  
  35. LOCALE_INEGSYMPRECEDES,             TEXT("LOCALE_INEGSYMPRECEDES"),  
  36. LOCALE_IOPTIONALCALENDAR,           TEXT("LOCALE_IOPTIONALCALENDAR"),  
  37. LOCALE_IPAPERSIZE,                  TEXT("LOCALE_IPAPERSIZE"),  
  38. LOCALE_IPAPERSIZE,                  TEXT("LOCALE_IPAPERSIZE"),  
  39. LOCALE_IPOSSIGNPOSN,                TEXT("LOCALE_IPOSSIGNPOSN"),  
  40. LOCALE_IPOSSYMPRECEDES,             TEXT("LOCALE_IPOSSYMPRECEDES"),  
  41. LOCALE_ITIME,                       TEXT("LOCALE_ITIME"),  
  42. LOCALE_ITIMEMARKPOSN,               TEXT("LOCALE_ITIMEMARKPOSN"),  
  43. LOCALE_ITLZERO,                     TEXT("LOCALE_ITLZERO"),  
  44. LOCALE_RETURN_NUMBER,               TEXT("LOCALE_RETURN_NUMBER"),  
  45. LOCALE_S1159,                       TEXT("LOCALE_S1159"),  
  46. LOCALE_S2359,                       TEXT("LOCALE_S2359"),  
  47. LOCALE_SABBREVCTRYNAME,             TEXT("LOCALE_SABBREVCTRYNAME"),  
  48. LOCALE_SABBREVDAYNAME1,             TEXT("LOCALE_SABBREVDAYNAME1"),  
  49. LOCALE_SABBREVDAYNAME2,             TEXT("LOCALE_SABBREVDAYNAME2"),  
  50. LOCALE_SABBREVDAYNAME3,             TEXT("LOCALE_SABBREVDAYNAME3"),  
  51. LOCALE_SABBREVDAYNAME4,             TEXT("LOCALE_SABBREVDAYNAME4"),  
  52. LOCALE_SABBREVDAYNAME5,             TEXT("LOCALE_SABBREVDAYNAME5"),  
  53. LOCALE_SABBREVDAYNAME6,             TEXT("LOCALE_SABBREVDAYNAME6"),  
  54. LOCALE_SABBREVDAYNAME7,             TEXT("LOCALE_SABBREVDAYNAME7"),  
  55. LOCALE_SABBREVLANGNAME,             TEXT("LOCALE_SABBREVLANGNAME"),  
  56. LOCALE_SABBREVMONTHNAME1,           TEXT("LOCALE_SABBREVMONTHNAME1"),  
  57. LOCALE_SABBREVMONTHNAME2,           TEXT("LOCALE_SABBREVMONTHNAME2"),  
  58. LOCALE_SABBREVMONTHNAME3,           TEXT("LOCALE_SABBREVMONTHNAME3"),  
  59. LOCALE_SABBREVMONTHNAME4,           TEXT("LOCALE_SABBREVMONTHNAME4"),  
  60. LOCALE_SABBREVMONTHNAME5,           TEXT("LOCALE_SABBREVMONTHNAME5"),  
  61. LOCALE_SABBREVMONTHNAME6,           TEXT("LOCALE_SABBREVMONTHNAME6"),  
  62. LOCALE_SABBREVMONTHNAME7,           TEXT("LOCALE_SABBREVMONTHNAME7"),  
  63. LOCALE_SABBREVMONTHNAME8,           TEXT("LOCALE_SABBREVMONTHNAME8"),  
  64. LOCALE_SABBREVMONTHNAME9,           TEXT("LOCALE_SABBREVMONTHNAME9"),  
  65. LOCALE_SABBREVMONTHNAME10,          TEXT("LOCALE_SABBREVMONTHNAME10"),  
  66. LOCALE_SABBREVMONTHNAME11,          TEXT("LOCALE_SABBREVMONTHNAME11"),  
  67. LOCALE_SABBREVMONTHNAME12,          TEXT("LOCALE_SABBREVMONTHNAME12"),  
  68. LOCALE_SABBREVMONTHNAME13,          TEXT("LOCALE_SABBREVMONTHNAME13"),  
  69. LOCALE_SCOUNTRY,                    TEXT("LOCALE_SCOUNTRY"),  
  70. LOCALE_SCURRENCY,                   TEXT("LOCALE_SCURRENCY"),  
  71. LOCALE_SDATE,                       TEXT("LOCALE_SDATE"),  
  72. LOCALE_SDAYNAME1,                   TEXT("LOCALE_SDAYNAME1"),  
  73. LOCALE_SDAYNAME2,                   TEXT("LOCALE_SDAYNAME2"),  
  74. LOCALE_SDAYNAME3,                   TEXT("LOCALE_SDAYNAME3"),  
  75. LOCALE_SDAYNAME4,                   TEXT("LOCALE_SDAYNAME4"),  
  76. LOCALE_SDAYNAME5,                   TEXT("LOCALE_SDAYNAME5"),  
  77. LOCALE_SDAYNAME6,                   TEXT("LOCALE_SDAYNAME6"),  
  78. LOCALE_SDAYNAME7,                   TEXT("LOCALE_SDAYNAME7"),  
  79. LOCALE_SDECIMAL,                    TEXT("LOCALE_SDECIMAL"),  
  80. LOCALE_SENGCOUNTRY,                 TEXT("LOCALE_SENGCOUNTRY"),  
  81. LOCALE_SENGCURRNAME,                TEXT("LOCALE_SENGCURRNAME"),  
  82. LOCALE_SENGLANGUAGE,                TEXT("LOCALE_SENGLANGUAGE"),  
  83. LOCALE_SGROUPING,                   TEXT("LOCALE_SGROUPING"),  
  84. LOCALE_SINTLSYMBOL,                 TEXT("LOCALE_SINTLSYMBOL"),  
  85. LOCALE_SISO3166CTRYNAME,            TEXT("LOCALE_SISO3166CTRYNAME"),  
  86. LOCALE_SISO639LANGNAME,             TEXT("LOCALE_SISO639LANGNAME"),  
  87. LOCALE_SLANGUAGE,                   TEXT("LOCALE_SLANGUAGE"),  
  88. LOCALE_SLIST,                       TEXT("LOCALE_SLIST"),  
  89. LOCALE_SLONGDATE,                   TEXT("LOCALE_SLONGDATE"),  
  90. LOCALE_SMONDECIMALSEP,              TEXT("LOCALE_SMONDECIMALSEP"),  
  91. LOCALE_SMONGROUPING,                TEXT("LOCALE_SMONGROUPING"),  
  92. LOCALE_SMONTHNAME1,                 TEXT("LOCALE_SMONTHNAME1"),  
  93. LOCALE_SMONTHNAME2,                 TEXT("LOCALE_SMONTHNAME2"),  
  94. LOCALE_SMONTHNAME3,                 TEXT("LOCALE_SMONTHNAME3"),  
  95. LOCALE_SMONTHNAME4,                 TEXT("LOCALE_SMONTHNAME4"),  
  96. LOCALE_SMONTHNAME5,                 TEXT("LOCALE_SMONTHNAME5"),  
  97. LOCALE_SMONTHNAME6,                 TEXT("LOCALE_SMONTHNAME6"),  
  98. LOCALE_SMONTHNAME7,                 TEXT("LOCALE_SMONTHNAME7"),  
  99. LOCALE_SMONTHNAME8,                 TEXT("LOCALE_SMONTHNAME8"),  
  100. LOCALE_SMONTHNAME9,                 TEXT("LOCALE_SMONTHNAME9"),  
  101. LOCALE_SMONTHNAME10,                TEXT("LOCALE_SMONTHNAME10"),  
  102. LOCALE_SMONTHNAME11,                TEXT("LOCALE_SMONTHNAME11"),  
  103. LOCALE_SMONTHNAME12,                TEXT("LOCALE_SMONTHNAME12"),  
  104. LOCALE_SMONTHNAME13,                TEXT("LOCALE_SMONTHNAME13"),  
  105. LOCALE_SMONTHOUSANDSEP,             TEXT("LOCALE_SMONTHOUSANDSEP"),  
  106. LOCALE_SNATIVECTRYNAME,             TEXT("LOCALE_SNATIVECTRYNAME"),  
  107. LOCALE_SNATIVECURRNAME,             TEXT("LOCALE_SNATIVECURRNAME"),  
  108. LOCALE_SNATIVEDIGITS,               TEXT("LOCALE_SNATIVEDIGITS"),  
  109. LOCALE_SNATIVELANGNAME,             TEXT("LOCALE_SNATIVELANGNAME"),  
  110. LOCALE_SNEGATIVESIGN,               TEXT("LOCALE_SNEGATIVESIGN"),  
  111. LOCALE_SPOSITIVESIGN,               TEXT("LOCALE_SPOSITIVESIGN"),  
  112. LOCALE_SSHORTDATE,                  TEXT("LOCALE_SSHORTDATE"),  
  113. LOCALE_SSORTNAME,                   TEXT("LOCALE_SSORTNAME"),  
  114. LOCALE_STHOUSAND,                   TEXT("LOCALE_STHOUSAND"),  
  115. LOCALE_STIME,                       TEXT("LOCALE_STIME"),  
  116. LOCALE_STIMEFORMAT,                 TEXT("LOCALE_STIMEFORMAT"),  
  117. LOCALE_SYEARMONTH,                  TEXT("LOCALE_SYEARMONTH")  
  118. };  
  119.   
  120. {  
  121.   
  122.     INT iLocaleNum = sizeof(g_localeAll)/sizeof(StruLocaleInfo);  
  123.   
  124.     INT i;  
  125.     TCHAR szToPrint[4096];  
  126.     for(i=0; i<iLocaleNum; i++)  
  127.     {  
  128.         if(0!=GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, g_localeAll[i].dwValue, szToPrint, 4096))  
  129.         {  
  130.             TRACE(TEXT("/n%s : %s"), g_localeAll[i].lpStrName, szToPrint);  
  131.         }  
  132.     }  
  133. }  

我的打印结果如下:
LOCALE_ICALENDARTYPE : 1
LOCALE_ICALENDARTYPE : 1
LOCALE_ICENTURY : 1
LOCALE_ICOUNTRY : 1
LOCALE_ICURRDIGITS : 2
LOCALE_ICURRENCY : 0
LOCALE_IDATE : 0
LOCALE_IDAYLZERO : 0
LOCALE_IDEFAULTANSICODEPAGE : 1252
LOCALE_IDEFAULTCODEPAGE : 437
LOCALE_IDEFAULTCOUNTRY : 1
LOCALE_IDEFAULTEBCDICCODEPAGE : 037
LOCALE_IDEFAULTLANGUAGE : 0409
LOCALE_IDEFAULTMACCODEPAGE : 10000
LOCALE_IDIGITS : 2
LOCALE_IDIGITSUBSTITUTION : 1
LOCALE_IFIRSTDAYOFWEEK : 6
LOCALE_IFIRSTWEEKOFYEAR : 0
LOCALE_IINTLCURRDIGITS : 2
LOCALE_ILANGUAGE : 0409
LOCALE_ILDATE : 0
LOCALE_ILZERO : 1
LOCALE_IMEASURE : 1
LOCALE_IMONLZERO : 0
LOCALE_INEGCURR : 0
LOCALE_INEGNUMBER : 1
LOCALE_INEGSEPBYSPACE : 0
LOCALE_INEGSIGNPOSN : 0
LOCALE_INEGSYMPRECEDES : 1
LOCALE_IOPTIONALCALENDAR : 0
LOCALE_IPAPERSIZE : 1
LOCALE_IPAPERSIZE : 1
LOCALE_IPOSSIGNPOSN : 3
LOCALE_IPOSSYMPRECEDES : 1
LOCALE_ITIME : 0
LOCALE_ITIMEMARKPOSN : 0
LOCALE_ITLZERO : 0
LOCALE_S1159 : AM
LOCALE_S2359 : PM
LOCALE_SABBREVCTRYNAME : USA
LOCALE_SABBREVDAYNAME1 : Mon
LOCALE_SABBREVDAYNAME2 : Tue
LOCALE_SABBREVDAYNAME3 : Wed
LOCALE_SABBREVDAYNAME4 : Thu
LOCALE_SABBREVDAYNAME5 : Fri
LOCALE_SABBREVDAYNAME6 : Sat
LOCALE_SABBREVDAYNAME7 : Sun
LOCALE_SABBREVLANGNAME : ENU
LOCALE_SABBREVMONTHNAME1 : Jan
LOCALE_SABBREVMONTHNAME2 : Feb
LOCALE_SABBREVMONTHNAME3 : Mar
LOCALE_SABBREVMONTHNAME4 : Apr
LOCALE_SABBREVMONTHNAME5 : May
LOCALE_SABBREVMONTHNAME6 : Jun
LOCALE_SABBREVMONTHNAME7 : Jul
LOCALE_SABBREVMONTHNAME8 : Aug
LOCALE_SABBREVMONTHNAME9 : Sep
LOCALE_SABBREVMONTHNAME10 : Oct
LOCALE_SABBREVMONTHNAME11 : Nov
LOCALE_SABBREVMONTHNAME12 : Dec
LOCALE_SABBREVMONTHNAME13 : 
LOCALE_SCOUNTRY : ??
LOCALE_SCURRENCY : $
LOCALE_SDATE : /
LOCALE_SDAYNAME1 : Monday
LOCALE_SDAYNAME2 : Tuesday
LOCALE_SDAYNAME3 : Wednesday
LOCALE_SDAYNAME4 : Thursday
LOCALE_SDAYNAME5 : Friday
LOCALE_SDAYNAME6 : Saturday
LOCALE_SDAYNAME7 : Sunday
LOCALE_SDECIMAL : .
LOCALE_SENGCOUNTRY : United States
LOCALE_SENGCURRNAME : US Dollar
LOCALE_SENGLANGUAGE : English
LOCALE_SGROUPING : 3;0
LOCALE_SINTLSYMBOL : USD
LOCALE_SISO3166CTRYNAME : US
LOCALE_SISO639LANGNAME : en
LOCALE_SLANGUAGE : ??(??)
LOCALE_SLIST : ,
LOCALE_SLONGDATE : dddd, MMMM dd, yyyy
LOCALE_SMONDECIMALSEP : .
LOCALE_SMONGROUPING : 3;0
LOCALE_SMONTHNAME1 : January
LOCALE_SMONTHNAME2 : February
LOCALE_SMONTHNAME3 : March
LOCALE_SMONTHNAME4 : April
LOCALE_SMONTHNAME5 : May
LOCALE_SMONTHNAME6 : June
LOCALE_SMONTHNAME7 : July
LOCALE_SMONTHNAME8 : August
LOCALE_SMONTHNAME9 : September
LOCALE_SMONTHNAME10 : October
LOCALE_SMONTHNAME11 : November
LOCALE_SMONTHNAME12 : December
LOCALE_SMONTHNAME13 : 
LOCALE_SMONTHOUSANDSEP : ,
LOCALE_SNATIVECTRYNAME : United States
LOCALE_SNATIVECURRNAME : US Dollar
LOCALE_SNATIVEDIGITS : 0123456789
LOCALE_SNATIVELANGNAME : English
LOCALE_SNEGATIVESIGN : -
LOCALE_SPOSITIVESIGN : 
LOCALE_SSHORTDATE : M/d/yyyy
LOCALE_SSORTNAME : ??
LOCALE_STHOUSAND : ,
LOCALE_STIME : :
LOCALE_STIMEFORMAT : h:mm:ss tt
LOCALE_SYEARMONTH : MMMM, yyyy
你的情况可能跟这个不一样,实验过程中,你可以自行修改下GetLocaleInfo的第一个参数,看看结果。那现在问题来了,这些信息对我们来说,有什么用呢?
当然是有用的,要不微软花那么大力气搞这些东西出来干什么?当用户在使用你的软件的时候,在你提供的界面里输入一个你看起来很奇怪的日期,你会认为这是个合法的日期吗?在你的角度看来,这是不正确的,可在用户的角度看来这是正确的,这是地域差距引起的。在中国,我们喜欢说“年月日”,到了美国就变成了“月日年”,到了英国则是“日月年”,你如何来“兼容”用户这些使用习惯,来使得你的软件“放之四海而皆准”?而日期格式的差异仅仅是其中的一个,别的差异还多得是,看看上面这段程序运行的结果,这些东西是不是你都要考虑呢?这个时候,也许你真体会到了“国际化”原来真的是个big problem,并不是简单的“翻译”。
事实上,我们是不太可能考虑得如此的完全,否则代价太高了,而一些地域上的差距,也没有想象中的严重,所以我们就选择其中一部分认为是有必要考虑的,来考虑。
不过从这里可以看出来,做一个通用软件有多么不容易……
前面在获取LCID过程中,我们调用了3个函数,分别是GetSystemDefaultLCID,GetUserDefaultLCID和GetThreadLocale(),它们取回来的结果分别是系统默认的LCID,当前用户默认的LCID和调用线程的LCID。
九、DBCS
我在文章一开始的时候就提到过,这个论题是非常零碎的,可不是,现在我又开始回头接第四节的内容,讲DBCS了,DBCS即是Double Byte Character Set,双字节字符集。有时候也写作MBCS,Multi-Byte Character Set,多字节字符集,我们现在可以认为MBCS等同于DBCS,将的都是同样的东西。但别跟DBMS混淆啊,DBMS是数据库管理系统,跟这个没关系。
DBCS为什么诞生?看看第四节的内容,就知道了,很大程度上是由于汉字的存在,那前面提到的GBK编码其实就是一种DBCS编码了。显然,我们在GBK编码的文件中写入英文和数字这些ASCII字符,它们被正常显示出来是没问题的,GBK编码跟ASCII兼容,那问题是我们的电脑怎么知道文章中哪些是ASCII字符,哪些是汉字呢?这个还得我们写点程序看看。

[cpp] view plaincopy
  1. {  
  2.     CPINFO info;  
  3.     UINT iCP = 936; //GBK  
  4.     ASSERT(GetCPInfo(iCP, &info));  
  5.     TRACE(TEXT("Code page %d's defalt char is [%c]/n"), iCP, info.DefaultChar[0]);  
  6.     TRACE(TEXT("Max size of a char: %d/n"), info.MaxCharSize);  
  7.     int i;  
  8.     const int iMaxLeadBytePairNum = 5;  
  9.     for(i=0; i<iMaxLeadBytePairNum; i++)  
  10.     {  
  11.         if(info.LeadByte[i*2]==0 && info.LeadByte[i*2+1]==0)  
  12.             break;  
  13.         TRACE(TEXT("Lead byte pair %d: 0x%02X-0x%02X/n"), i, info.LeadByte[i*2], info.LeadByte[i*2+1]);  
  14.     }  
  15. }  

这段代码将帮助你寻找答案。这是我的运行结果:
Code page 936's defalt char is [?]
Max size of a char: 2
Lead byte pair 0: 0x81-0xFE
第一个Default char的意思是当进行内码转换时候,把无法转换的字符置为这个Default char,在我这里显示出来是个“?”,一个问号,这也是系统给出的默认值,这会你可知道这个小问号的起源了吧?当你使用英文系统,对一个用GBK编码的文本文件读入,并用MultiByteToWideChar这个函数转换为Unicode(后面再具体讨论)显示出来的时候,你就往往会发现这种问号,这也是Windows给出的默认的转换失败符号,所以一般情况下,你看到这个小问号,就知道,在什么地方转换失败了,小问号是转换的时候产生的,现在可能你还不是很理解,但没关系,我后面还会强调。
第二个打印结果,Max size是2,这是说这个code page一个字符最大占用2个字节,事实上,在GBK编码中,所有中文字符占用的都是两个字节。
第三个打印出来的结果,这是关键的,Windows如何来识别接下去的一个字符是不是双字节字符,就靠这个Lead byte了,Windows会判断接下去的这个byte是否处于0x81到0xFE这个范围之内,如果是,则把这个byte和它的后一个byte看作是一个字符。从程序运行结果看得出,GBK的Lead byte只有一个区间,就是0x81-0xFE,我不清楚其它DBCS编码是否有多个区间,按MSDN上的说法,最多能容纳下5个区间,这个大家可以自行尝试了,比如看看日文Shift-JIS编码(932)的运行情况,我就不直接公布答案了。
大家想想看,这种DBCS编码方式存在什么问题呢?看看下面这个图:

 dbcs correct

这是正常的,显示出来没有任何问题,但如果由于某些原因,漏掉了一个byte,情况会怎么样呢?如图所示:

dbcs incorrect

是不是后面的字符都乱了?那为什么会漏掉这个字节?我现在也不知道怎么说,但这种情况以前经常发生,在DOS时代的时候,我们是使用一些外挂程序来处理中文的,最有名的一个叫UCDOS,运行了它,才能运行WPS,(如果你不知道WPS这个是什么的话,我强烈建议你马上到网上搜一下),当然很多人都不知道得这样干,他们只想用WPS打字,于是就给他们弄个傻瓜式的批处理文件,利用UCDOS,我们就能够输入汉字了,UCDOS有个可爱的特点,那就是能够删除半个汉字,这听起来有些不可思议,但以前确实可以,我现在已经找不到这么老的软件试给大家看了,由于允许删除半个汉字,后面的汉字解释就遇到了问题,当然电脑是不会认为有问题的,而我们读起来就完全不对了。有没有可能网络传输的时候漏掉一个byte呢?这个我也没法试验,可能性确实有吧,我想……而之后我会讲到UTF-8,就比较好地解决了这个问题。
本篇就先讲那么多,下一篇准备转入最重要的部分——UNICODE。应该这几天能整理好。
本篇小结
一个Locale对应许多语言相关的格式,它还对应一个ANSI code page和一个OEM codepage,同一个Locale的ANSI code page和OEM code page有些时候相同,比如中文(中国),有些时候不同,比如英语(美国)。OEM编码的文本要在Windows环境下有较好的显示,通常需要转换。DBCS是汉字处理的较早解决方法,DBCS是根据Lead byte来判断接下去的内容是否是代表一个双字节字符,这种方法容易由于丢失一个字节而产生通篇的乱码。

0 0
原创粉丝点击