Windows:直接查看cp1252

Windows: directly examine cp1252

让我先声明:我绝不是 Windows 程序员。请帮助我纠正我可能有的任何误解。

我的理解是Windows既有(传统的)单字节字符串接口也有现代化的 Unicode 接口。

我的目标是仔细检查 Windows 内核中实现的 cp1252。我将从 Windows XP 开始,但我计划检查尽可能多的版本。

我打算让这样一个程序的输出格式类似于:https://encoding.spec.whatwg.org/index-windows-1252.txt

我的问题主要是:我将使用哪些 Windows API 函数来完成上述任务?我认为是 mbstowcs_s.

其次:一定要写C才能查看相关接口吗?如果是这样,我会使用什么编译器?我认为 Visual Studio Express 2010 是一个很好的匹配,但我找不到任何(合法)下载它的地方。


对于那些必须知道 X 和 Y 的人来说,cp1252 有两个相互竞争的标准和实现。它们只是略有不同,但确实不同,这对我来说很重要。

WHATWG 规定,所有浏览器都执行此标准: https://encoding.spec.whatwg.org/index-windows-1252.txt

Microsoft 指定,python 实施此标准: http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT

区别在于五个不可打印的字符。在 windows 规范中,它们完全未定义,因此这些字节不能通过 cp1252 来回传输。在 WHATWG 规范(和所有浏览器)中,这些字节映射到具有相同值的非打印字符,如在 latin1 中,这意味着这些字节可以通过 cp1252 成功往返。

我强烈怀疑 Microsoft 的实现实际上与 WHATWG 规范和浏览器的实现相匹配,而不是他们发布的规范。这就是我在上面 prove/disprove 所做的。

你的问题没有任何意义。您想要检查从 95 到 10 的每个 Windows 版本使用的 "the encoding"。

但是 Windows 的那些版本中的 none 有 "an encoding"。它们中的每一个都可以以相同的方式配置:它有一个默认的系统编码,由 Microsoft 预先配置,以及一个当前的用户编码,由 Microsoft 或系统 OEM 设置,但用户可以更改。因此,您的测试将不依赖于 Windows 95 与 Windows 7,而是依赖于 Microsoft 的默认设置为 US Windows 95 与 ES Windows 95来自 Microsoft 的默认设置与美国 Windows 95 来自 HP 的默认设置与美国 Windows 来自 Microsoft 的 95 以及控制面板等中的 238 个可能选项中的每一个

此外,要生成您尝试生成的文件类型,您无需接触任何 Win32 API。您需要做的就是调用任何使用配置的系统区域设置字符集的函数,将 single-byte/multi-byte 文本解码为 UTF-16/Unicode 文本。例如,从 C 中,您可以从 MSVCRT 中调用 mbcstowcs 家族之一;从 Python,您可以在 str (Python 2)/bytes (Python 3) 对象上调用 decode 方法 sys.getdefaultencoding();等等

如果您真的想使用系统界面来测试相同的信息,您可以……但是您会 运行 进入大多数界面的限制。例如,您可以 CreateFileA 创建一个具有 8 位名称的新文件,然后尝试 CreateFileW 以相应的 16 位名称打开同一个文件并验证它是否有效……但是随后您无法测试任何非法文件名字符。

最后,Microsoft 已经 为大多数(如果不是全部的话)这些平台提供了免费的 C 编译器,但是其中一些已经停止服务很久了,所以我不知道你是否能不能(至少合法地)得到它们。但是您始终可以使用 MinGW 来设置基于 gcc 的工具链。不知道现在的版本在Win95上还能不能用,如果不能,老版本应该还是可以的。

要回答你的 X 问题而不是你的 Y 问题:

你真的不能问 "Windows" 如何处理它所谓的 "ANSI strings",因为有多个不同的级别独立处理它们。他们都以兼容的方式这样做是一个很好的赌注……但你的重点是避免那个很好的赌注并直接检查真相。

我认为您可以安全地假设 MultiByteToWideChar 会为您提供与在 Win32 API 中调用 SpamA 和 SpamW 函数相同的结果。 (如果你甚至不能假设,我认为你真的需要测试 API 中的每个函数对以确保它们都有相同的结果......)你可以直接传递 CP_1252 ,但我认为在为 1252 配置的系统上传递 CP_OEMCP 可以更好地测试您的要求。或者两者都做。

MSVCRT(处理提供基于 8 位字符串的标准 C 接口和大块 POSIX 给可移植程序,包括 CPython)似乎有其自己的转换。要验证这一点,请致电 mbstowcs 或其亲属之一。

我很确定 Win32 系统层以与用户层相同的方式处理 ANSI 字符串,但您可能想要搜索未记录的 ZwMultiByteToWideChar 或类似的。而且我认为内核不会在任何地方处理 ANSI 字符串——例如,IIRC,当你编写一个文件系统驱动程序时,唯一的路径名接口很宽……但你可能想要下载 DDK 并确保我是对的。

我认为 Explorer GUI shell 依赖 Win32 层来处理所有事情,并且不会在任何地方触及 ANSI 字符串。 cmd.exe 命令行 shell 只处理 Unicode(Win9x 上的 运行 DOS 程序除外)——但它也是一个终端,作为一个终端,它确实实际上处理 ANSI 和 Unicode 字符串并映射它们。特别是,您可以发送 ANSI 或 Unicode 控制台输出并读取 ANSI 或 Unicode 控制台输入。这可能是通过 MultiByteToWideChar 和朋友完成的,但我不能保证。我认为 MSVCRT 的 stdin/out 和 wstdin/out 及其 DOS-conio-style getch/etc. and getwch/etc. functions just access these respective console APIs instead of translating in MSVCRT, but if you don't trust that, you can go around it and either get the native console streams or just call the Console I/O 直接起作用。

那么,如何为这些东西编写测试程序,而不会发现多个不受支持的 Microsoft C++ 编译器版本和每个 OS 的 SDK? (而且,即使您这样做了,您如何确定 WinXP SDK 的更高版本没有对您隐藏 XP 本身存在的问题?)

答案是 LoadLibrary and GetProcAddress 函数在运行时脱离各自的 DLL。您可以通过为 Windows.

的一个版本编译的程序来执行此操作

或者,甚至更简单,只需使用 Python,并使用其 ctypes 模块访问 DLL 中的函数。只需确保明确创建并传递 LPSTRLPWSTR 缓冲区,而不是在任何地方传递 str/bytes/unicode 对象。


所以最终,我认为您只需要一个 20 行的 Python 脚本,该脚本使用 ctypesKERNEL32.DLL 或 [=13= 中调用 MultiByteToWideChar ] 在 MSVCRT32.DLL 或两者中。

在@abernert 的帮助下,我想到了这个。总之,正如我所怀疑的那样,Microsoft 的规范与他们的实现不匹配:

from ctypes import cdll, windll, c_char_p
c = cdll.msvcrt
k = windll.kernel32
LC_ALL = 0  # from locale.h
# reference: https://msdn.microsoft.com/en-US/library/x99tb11d.aspx
c.setlocale.restype = c_char_p
result = c.setlocale(LC_ALL, '.1252')
assert result == 'English_United States.1252', result

from ctypes import create_string_buffer
# cp1252 is classified as "multi-byte" by the msapi along with utf8
mb = create_string_buffer(1)
wc1 = create_string_buffer(2)
wc2 = create_string_buffer(2)

print 'IN | MSVC  KERN'
print '---+-----------'
for b in range(0x80, 0xA0):
    mb.value = chr(b)

    # reference: https://msdn.microsoft.com/en-us/library/yk02bkxb.aspx
    result = c.mbtowc(wc1, mb, 1)
    assert result == 1, result

    # reference:
    #     https://msdn.microsoft.com/en-us/library/windows/desktop/dd319072.aspx
    result = k.MultiByteToWideChar(1252, 0, mb, 1, wc2, 1)
    assert result == 1, result

    print '%02X | %02X%02X  %02X%02X' % (
        ord(mb.value),
        # little-endian:
        ord(wc1.raw[1]), ord(wc1.raw[0]),
        ord(wc2.raw[1]), ord(wc2.raw[0]),
    )

输出:(在 Windows XP、Vista、7、8.1 上测试)

IN | MSVC  KERN
---+-----------
80 | 20AC  20AC
81 | 0081  0081
82 | 201A  201A
83 | 0192  0192
84 | 201E  201E
85 | 2026  2026
86 | 2020  2020
87 | 2021  2021
88 | 02C6  02C6
89 | 2030  2030
8A | 0160  0160
8B | 2039  2039
8C | 0152  0152
8D | 008D  008D
8E | 017D  017D
8F | 008F  008F
90 | 0090  0090
91 | 2018  2018
92 | 2019  2019
93 | 201C  201C
94 | 201D  201D
95 | 2022  2022
96 | 2013  2013
97 | 2014  2014
98 | 02DC  02DC
99 | 2122  2122
9A | 0161  0161
9B | 203A  203A
9C | 0153  0153
9D | 009D  009D
9E | 017E  017E
9F | 0178  0178

将此与 Microsoft 在 unicode.org 注册的 the spec 进行比较:

0x80    0x20AC  #EURO SIGN
0x81            #UNDEFINED
0x82    0x201A  #SINGLE LOW-9 QUOTATION MARK
0x83    0x0192  #LATIN SMALL LETTER F WITH HOOK
0x84    0x201E  #DOUBLE LOW-9 QUOTATION MARK
0x85    0x2026  #HORIZONTAL ELLIPSIS
0x86    0x2020  #DAGGER
0x87    0x2021  #DOUBLE DAGGER
0x88    0x02C6  #MODIFIER LETTER CIRCUMFLEX ACCENT
0x89    0x2030  #PER MILLE SIGN
0x8A    0x0160  #LATIN CAPITAL LETTER S WITH CARON
0x8B    0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
0x8C    0x0152  #LATIN CAPITAL LIGATURE OE
0x8D            #UNDEFINED
0x8E    0x017D  #LATIN CAPITAL LETTER Z WITH CARON
0x8F            #UNDEFINED
0x90            #UNDEFINED
0x91    0x2018  #LEFT SINGLE QUOTATION MARK
0x92    0x2019  #RIGHT SINGLE QUOTATION MARK
0x93    0x201C  #LEFT DOUBLE QUOTATION MARK
0x94    0x201D  #RIGHT DOUBLE QUOTATION MARK
0x95    0x2022  #BULLET
0x96    0x2013  #EN DASH
0x97    0x2014  #EM DASH
0x98    0x02DC  #SMALL TILDE
0x99    0x2122  #TRADE MARK SIGN
0x9A    0x0161  #LATIN SMALL LETTER S WITH CARON
0x9B    0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
0x9C    0x0153  #LATIN SMALL LIGATURE OE
0x9D            #UNDEFINED
0x9E    0x017E  #LATIN SMALL LETTER Z WITH CARON
0x9F    0x0178  #LATIN CAPITAL LETTER Y WITH DIAERESIS

我很清楚标记为 UNDEFINED 的插槽(字节 81 8D 8F 90 和 9D)不是未定义的,不是错误,而是解码为相同序号的不可打印字符,就像它们在 [=16 中所做的那样=], 下面:

  0 0x20AC  € (EURO SIGN)
  1 0x0081   (<control>)
  2 0x201A  ‚ (SINGLE LOW-9 QUOTATION MARK)
  3 0x0192  ƒ (LATIN SMALL LETTER F WITH HOOK)
  4 0x201E  „ (DOUBLE LOW-9 QUOTATION MARK)
  5 0x2026  … (HORIZONTAL ELLIPSIS)
  6 0x2020  † (DAGGER)
  7 0x2021  ‡ (DOUBLE DAGGER)
  8 0x02C6  ˆ (MODIFIER LETTER CIRCUMFLEX ACCENT)
  9 0x2030  ‰ (PER MILLE SIGN)
 10 0x0160  Š (LATIN CAPITAL LETTER S WITH CARON)
 11 0x2039  ‹ (SINGLE LEFT-POINTING ANGLE QUOTATION MARK)
 12 0x0152  Œ (LATIN CAPITAL LIGATURE OE)
 13 0x008D   (<control>)
 14 0x017D  Ž (LATIN CAPITAL LETTER Z WITH CARON)
 15 0x008F   (<control>)
 16 0x0090   (<control>)
 17 0x2018  ‘ (LEFT SINGLE QUOTATION MARK)
 18 0x2019  ’ (RIGHT SINGLE QUOTATION MARK)
 19 0x201C  “ (LEFT DOUBLE QUOTATION MARK)
 20 0x201D  ” (RIGHT DOUBLE QUOTATION MARK)
 21 0x2022  • (BULLET)
 22 0x2013  – (EN DASH)
 23 0x2014  — (EM DASH)
 24 0x02DC  ˜ (SMALL TILDE)
 25 0x2122  ™ (TRADE MARK SIGN)
 26 0x0161  š (LATIN SMALL LETTER S WITH CARON)
 27 0x203A  › (SINGLE RIGHT-POINTING ANGLE QUOTATION MARK)
 28 0x0153  œ (LATIN SMALL LIGATURE OE)
 29 0x009D   (<control>)
 30 0x017E  ž (LATIN SMALL LETTER Z WITH CARON)
 31 0x0178  Ÿ (LATIN CAPITAL LETTER Y WITH DIAERESIS)