难得我写系列文章,我的作风更偏向于一鼓作气。(那接下去怎么说?再而衰,三而竭……希望不是这样,呵呵……)
七、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。

这个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文件查看,会怎么样呢?我这里有个截图:

要有这种显示,得按照上一篇文章说的那样,在控制面板里的“区域和语言选项”里,把“非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. }

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

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

上图画得我好累,给点掌声吧……浅绿色横条是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. INT iLocaleNum = sizeof(g_localeAll)/sizeof(StruLocaleInfo);
  121. INT i;
  122. TCHAR szToPrint[4096];
  123. for(i=0; i<iLocaleNum; i++)
  124. {
  125. if(0!=GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, g_localeAll[i].dwValue, szToPrint, 4096))
  126. {
  127. TRACE(TEXT("/n%s : %s"), g_localeAll[i].lpStrName, szToPrint);
  128. }
  129. }
  130. }

我的打印结果如下:
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编码方式存在什么问题呢?看看下面这个图:

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

是不是后面的字符都乱了?那为什么会漏掉这个字节?我现在也不知道怎么说,但这种情况以前经常发生,在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来判断接下去的内容是否是代表一个双字节字符,这种方法容易由于丢失一个字节而产生通篇的乱码。

国际化困境(第二篇)相关推荐

  1. 【.NET Core 跨平台 GUI 开发】第二篇:Gtk# 布局入门,初识HBox 和 VBox

    这是 Gtk# 系列博文的第二篇.在上一篇博文<编写你的第一个 Gtk# 应用>中,我们提到"一个 Gtk.Window 只能直接包含一个部件".这意味着,在不做其他额 ...

  2. 使用 ASP.NET Core, Entity Framework Core 和 ABP 创建N层Web应用 第二篇

    介绍 这是"使用 ASP.NET Core ,Entity Framework Core 和 ASP.NET Boilerplate 创建N层 Web 应用"系列文章的第二篇.以下 ...

  3. Android 进阶第二篇——性能优化

    Android 进阶第二篇--性能优化 一些Android书籍喜欢把性能优化放在最后的章节,简单提一提作为内容全面的点缀.在这里我将工具使用和性能优化的一些个人经验放在进阶系列博客的开始,因为我认为防 ...

  4. Django框架之第二篇

    Django框架之第二篇 一.知识点回顾 1.MTV模型 model:模型,和数据库相关的 template:模板,存放html文件,模板语法(目的是将变量如何巧妙的嵌入到HTML页面中). view ...

  5. java设计模式中不属于创建型模式_23种设计模式第二篇:java工厂模式定义:工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式...

    23种设计模式第二篇:java工厂模式 定义: 工厂模式是 Java 中最常用的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 工厂模式主要是为创建对象提供过渡接口, ...

  6. 深入理解javascript函数系列第二篇——函数参数

    前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数.本文是深入理解javascript函数 ...

  7. Spotify敏捷模式详解三部曲第二篇:研发过程

    本文转自:Scrum 中文网 引言 在本系列文章的第一篇,我们介绍了Spotify的敏捷研发团队,以及它独特的组织架构.Spotify的研发团队采用的是一种非常独特的组织架构,如下图所示: 整个研发组 ...

  8. SAP PP COR2下达工单系统报错说-系统状态APNG是激活的- 分析第二篇

    SAP PP COR2下达工单系统报错说-系统状态APNG是激活的- 分析第二篇 笔者所在的项目上启用了ECM(Engineer Change Management)功能,重要数据的修改都要事先创建一 ...

  9. 学习动态性能表 第二篇--v$sesstat

    学习动态性能表 第二篇--v$sesstat  按照OracleOnlineBook中的描述,v$sesstat存储session从login到logout的详细资源使用统计. 类似于v$syssta ...

  10. 初学Python——文件操作第二篇

    前言:为什么需要第二篇文件操作?因为第一篇的知识根本不足以支撑基本的需求.下面来一一分析. 一.Python文件操作的特点 首先来类比一下,作为高级编程语言的始祖,C语言如何对文件进行操作? 字符(串 ...

最新文章

  1. 三十二、Java集合中的ArrayList
  2. RocketMQ事务消息的三种状态
  3. quantum theory
  4. [AHOI2004]数字迷阵 结论+矩乘
  5. 前端名称命名--英文字母
  6. 1.2.3 TCP/PI参考模型(应用层、传输层、网际层、网络接口层)、五层参考模型(应用层、传输层、网络层、数据链路层、物理层)、OSI与TCP/IP参考模型比较(转载)
  7. python几种设计模式_Python七大原则,24种设计模式
  8. VB编辑器之代码颜色修改
  9. UG NX 12 坐标系的操作
  10. 动手学深度学习 v2 PDF版本
  11. 安卓DEVICE ID为何有15位和16位
  12. 全智通A+常见问题汇总解答—A+配件仓库—维修领料—编辑领料单:最后一个仓库无法显示
  13. POJ-3368 Frequent values
  14. 游戏渠道SDK是什么
  15. windows使用scp远程传输文件的方法
  16. TypeError: Converting circular structure to JSON
  17. HDMI各版本的区别
  18. quill——简单的富文本编辑器
  19. 计算机系统的体系结构论文,计算机系统结构参考文献
  20. 基于k8s+Prometheus+Alertmanager+Grafana构建企业级监控告警系统

热门文章

  1. keil中文乱码解决和个人习惯字体设置
  2. Silverlight实用窍门系列:47.Silverlight中元素到元素的绑定,以及ObservableCollection和List的使用区别...
  3. iOS NSArray数组过滤
  4. HBase性能优化方法总结(四):数据计算
  5. WorkFlow设计篇Step.2—传参的用法-订单金额的处理(续)-WF4.0
  6. [bat] 使用bat文件保证指定程序运行
  7. 用自定义IHttpModule实现URL重写
  8. 【中国超算迎来最强对手】 IBM推出机器学习加速“瑞士军刀”Power9芯片,性能为同类产品的10倍...
  9. 避无可避:Mesos安全问题的几点思考
  10. spring对session和事务的管理以及OpenSessionInViewFilter是如何工作