为什么 ANSI 代码页和控制台代码页不同?

8

Microsoft Windows提供了几个函数来查询当前的代码页: GetACP, GetConsoleOutputCP, 以及 GetConsoleCP

它们返回不同的值。例如,在我的机器上,GetACP 返回1252,而 GetConsoleOutputCPGetConsoleCP 返回437。

(我们也可以在命令行上运行 chcp 并得到 437)

  • 为什么Windows为控制台和非控制台提供不同的代码页?
  • 这些代码页如何由每台机器确定?
  • 同一台机器上的代码页之间有什么关系?控制台和非控制台代码页之间是否存在相关性?具有代码页1252的机器是否总是具有437的控制台代码页?

这个问题的背景是来自Visual Studio C++的错误信息:

error C2855: command-line option '/source-charset' inconsistent with precompiled header
error C2855: command-line option '/execution-charset' inconsistent with precompiled header

当预编译头文件所使用的默认代码页与使用它们的CPP文件的代码页不同时(出于某些原因),会出现这些错误。
以下摘自MSDN文档

如果没有找到字节顺序标记,则假定源文件使用当前用户代码页进行编码,除非您使用 /source-charset 选项指定字符集名称或代码页。

所以我正在尝试弄清楚他们所指的那个代码页是指由GetACP返回的代码页还是其他代码页...

1
兼容性,控制台子系统旨在帮助移植在MS-Dos时代编写的程序。当微软向许多OEM销售DOS时,这是一个重要的市场。代码页437和光栅字体重新创建了原始的IBM-PC字符集。850在欧洲大陆很常见,具有更多的字形来显示变音符号。下一个重新设计代码页的机会更加注重正确呈现文本,出现在Windows中。 - Hans Passant
4个回答

16

系统的 ANSI 和 OEM 代码页由系统启动时加载的系统语言环境决定。它们被映射到每个进程中作为 PEB 字段 AnsiCodePageDataOemCodePageData。在 ntdll.dll 中的运行时库有许多函数可以处理这些字符串类型,例如RtlAnsiStringToUnicodeStringRtlOemStringToUnicodeString

Windows API 中以 A 结尾的函数是 ANSI 的,但是文件系统函数可以通过SetFileApisToOEM 切换到 OEM。控制台 API 默认使用 OEM 以兼容旧应用程序,并可通过SetConsoleCPSetConsoleOutputCP更改为其他代码页。 chcp.com (或mode.com) 调用这些函数,但它不允许将输入缓冲区和屏幕缓冲区设置为不同的代码页。

如果 ANSI 代码页是 1252,则 OEM 代码页不一定是 437。这只适用于美国语言环境。大多数使用 1252 作为 ANSI 代码页的西方语言环境将使用 850 作为 OEM 代码页。

声称正在使用用户代码页的应用程序可能不是指系统 ANSI 或 OEM 代码页。相反,它可能会调用GetLocaleInfoEx来查询LOCALE_NAME_USER_DEFAULT语言环境的LOCALE_IDEFAULTANSICODEPAGELOCALE_IDEFAULTCODEPAGE


2
对于那些点踩的人,如果你没有解释就点踩,那是你的权利。但至少给一点反馈会更有帮助,让我知道哪里有问题;是否有改进答案的方法,或者你的原因足够重要,我应该删除这个答案。 - Eryk Sun
这个踩贴的人可能是在恶意刷屏。问题被踩了,但没有解释。现在你最后提到的事情让我感到困惑。除了 ANSI 和 OEM,我们还有其他的代码页吗?根据这个 MSDN 页面LOCALE_IDEFAULTANSICODEPAGE 返回 ANSI 代码页,LOCALE_IDEFAULTCODEPAGE 返回 OEM 代码页。在什么情况下它们会与 GetACPGetConsoleCP 等返回的代码页不同? - Amir Gonnen
正如我所提到的,系统区域设置是Windows ANSI API中使用的“A”后缀函数所使用的。它通常使用系统ANSI代码页,但文件系统API可以切换到系统OEM代码页,并且控制台默认为OEM。ANSI API已被弃用。程序应该使用“W”后缀的Unicode API,许多新函数如GetLocaleInfoEx甚至没有ANSI实现。 - Eryk Sun
最好将文本保存为带有BOM的UTF-8或UTF-16格式,而不是使用传统的代码页。但我们并没有与过去隔绝,许多情况下仍然需要使用代码页。 - Eryk Sun

3
命令控制台出于历史原因使用不同的代码页。在控制台上运行的程序通常是为DOS编写的,并且字符集包括在此情况下有用的线条绘制字符。在具有本机Windows应用程序的图形环境中,扩展可用字符更为重要,因为线条将直接绘制而不是在字体中模拟。
默认代码页由Windows将使用的语言确定。不同的语言需要不同的字符,一个代码页无法容纳所有欧洲语言使用的字符。例如,在某些中欧和东欧地区会使用代码页1250

2
为什么Windows为控制台和非控制台提供不同的代码页?
这是因为为了与MS-DOS应用程序保持向后兼容性,这些应用程序仍然可以在16位和32位的Windows上运行,并且其中许多应用程序也被移植到了Windows控制台。此外,使用DOS中的Alt code的能力已经深入人心,用户如果不能再输入他们喜欢的特殊字符,他们会抱怨不已,所以DOS代码页是必需的。
DOS最初使用的是内置于EGA和VGA ROM中的代码页437。但后来ISO和IEC联合制定了新的标准代码页,因此微软迅速采用了代码页1252作为Windows的基础,该代码页基于后来成为ISO 8859-1的早期草案。
这源于Windows代码页1252最初基于一个ANSI草案,后来成为ISO标准8859-1的事实。
事实上,微软一直是早期采用者。例如,它是第一个采用韩国标准的,并且是第一个使用Unicode的,这两者后来都被后悔了。前者从未被其他人使用过,而后者使得编写可移植代码变得困难。其他人后来都采用了更新更好的UTF-8。
微软非常重视向后兼容性,因此在引入新的Windows代码页时,他们无法更改控制台应用程序的行为。因此,他们只能对GUI应用程序进行更改。因此,在Unicode出现之前的传统Windows GUI应用程序将使用ANSI代码页,并且仍将维护一个单独的代码页用于控制台应用程序。还需要引入一种不同的输入特殊字符的方法:这是通过Alt键后的第一个数字键来区分的。
  • 如果是numpad 1-9,则使用DOS代码页(也称为OEM代码页)。按下Alt+7会产生代码点7(在CP437中为U+2022 "•")

  • 如果是numpad 0,则使用Windows代码页(也称为ANSI代码页)。按下Alt+0149会产生代码点149,与CP1252中的U+2022 "•"相同

  • 如果是numpad +,则输入为十六进制UCS2/UTF-16。这是新的Windows图形界面应用程序使用Unicode的行为。键入Alt++2022会得到相同的U+2022 "•"字符

    请注意,这需要通过在注册表项HKCU\Control Panel\Input Method中设置一个名为EnableHexNumpadREG_SZ值,然后重新启动来启用十六进制小键盘。

另请参阅ALT键的字符编码使用了哪种字符编码?
这些代码页是如何根据每台机器确定的?
每个区域设置都有4个不同的默认关联代码页:OEM(DOS)、ANSI(Windows)、EBCDIC和Mac(经典)代码页,其中只有前两个在现在实际上很重要。因此,在安装Windows之后的默认美国区域设置中,你将分别拥有CP437和CP1252作为DOS和Windows代码页。但是这些可以很容易地被改变,例如通过使用chcp命令,通过API调用或者通过编辑注册表。

同一台机器上的代码页之间有什么关系?控制台和非控制台代码页之间是否存在相关性?
它们之间唯一的关系是与区域设置的连接。
不,因为用户可以更改代码页。此外,可能存在使用CP1252但默认使用其他DOS代码页的非美国区域设置。

1

这些代码页是如何确定的?

请查看此表格国际化语言支持 (NLS) API 参考

或者查询您的注册表:

C:\>reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage /v OEMCP

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage
    OEMCP    REG_SZ    850


C:\>reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage /v ACP

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage
    ACP    REG_SZ    1252

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接