使用 Windows WinAPI 函数写入控制台 Unicode (UTF-16) 文本?

Lor*_*nzo 3 unicode console assembly x86-64 masm

我有一个 64 位 masm 代码,可以输出到控制台。问题是,通过使用WriteConsoleW,我无法重定向命令或任何内容的输出,因为它只写入控制台缓冲区。但使用WriteFile会在每个字符之间添加空格,因为 16 位字符的高位已清零。如何使用 打印 Unicode 文本WriteFile

在这里读到我可以使用 BOM,但这对我不起作用(我添加了另一个调用,该调用在第二次调用之前WriteFile写入两个字节,但它只打印了一个白色矩形,没有其他内容)。FF FEWriteFile

这是代码:

extern GetStdHandle: proc
extern WriteConsoleW: proc
.data?
    written dq ?
.data
    string dw 0048h,0065h,006ch,006ch,006fh,0020h,0057h,006fh,0072h,006ch,0064h,0021h
    len equ $-string
.code
main proc
    push    rbp
    mov rbp, rsp
    sub rsp, 020h
    and rsp, -10h

    mov rcx, -11
    call    GetStdHandle
    mov rcx, rax
    mov rdx, offset string
    mov r8, len
    mov r9, written
    call    WriteConsoleW

    add rsp, 020h
    mov rsp, rbp
    pop rbp
    ret
main endp
end
Run Code Online (Sandbox Code Playgroud)

当我交换WriteConsoleW到 时WriteFile,它在通过 Visual Studio 运行时正确打印,但是当我运行exe从命令行生成的文件时,Hello World!它不是打印而是打印H e l l o W o r l d !

有谁知道如何处理这个问题?

编辑:我不知道如何理解这一点,但不知何故,当我使用WriteFile它时,只有当我单独执行程序时,16 位字符才会打印错误。但是,当我将输出重定向到echo命令时,它会正常打印: Powershell打印输出

Mar*_*nen 5

C++ 中的相同 API 会产生相同的控制台输出。 WriteConsoleW对控制台执行字符转换,但WriteFile不执行。\nWriteFile只是将字节发送到控制台,控制台在当前代码页中解释它们,对我来说是 437(美国 OEM)。

\n

我可以通过调用SetConsoleOutputCP(65001)(将控制台代码页设置为 UTF-8)然后编写 UTF-8 字符串来使其在 C++ 中工作。请注意此代码页标识符列表,其中包括 UTF-16,但它仅适用于托管应用程序(例如 C#)。

\n

我打印了一些非 ASCII 来看看它是否正确输出。

\n
// compiled with MSVS "cl /W4 /utf-8 test.cpp"\n// source saved in UTF-8 as well.\n#include <windows.h>\n\nint main() {\n    char s[] = u8"Hello, \xe9\xa9\xac\xe5\x85\x8b"; // Note: need a chinese font, but cut/paste\n                               // to Notepad and you\'ll see them if you don\'t.\n    SetConsoleOutputCP(65001);\n    auto h = GetStdHandle(STD_OUTPUT_HANDLE);\n    DWORD written;\n    WriteFile(h, s, sizeof(s), &written, nullptr);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出:

\n
Hello, \xe9\xa9\xac\xe5\x85\x8b\n
Run Code Online (Sandbox Code Playgroud)\n

您应该能够轻松地将其适应 MASM。

\n

如果您愿意使用 C 运行时库,那么如果您正确设置控制台和文件模式,这些 API 都适用于 UTF-16:

\n
#include <stdio.h>\n#include <io.h>\n#include <fcntl.h>\n\nint main()\n{\n    _setmode(_fileno(stdout), _O_U16TEXT);\n    wchar_t s[] = L"Hello, \xe9\xa9\xac\xe5\x85\x8b!";\n    _write(_fileno(stdout), s, sizeof(s));\n    int fd = _open("test.txt", _O_CREAT | _O_WRONLY | _O_U16TEXT);\n    _write(fd, s, sizeof(s));\n    _close(fd);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出到控制台:

\n
Hello, \xe9\xa9\xac\xe5\x85\x8b!\n
Run Code Online (Sandbox Code Playgroud)\n

输出到以 UTF-16LE 编码的 test.txt。请注意,\xe9\xa9\xac\xe5\x85\x8b 是两个 unicode 代码点 U+9A5C 和 U+514B:\ntest.txt 的十六进制转储

\n

编辑

\n

这是一个GetFileType. 如果运行它会正确写入控制台。如果重定向到文件,例如“test > out.txt”,则输出文件包含UTF-16LE 编码的数据。

\n
#include <windows.h>\n\nint main()\n{\n    auto h = GetStdHandle(STD_OUTPUT_HANDLE);\n    auto type = GetFileType(h);\n    \n    WCHAR s[] = L"Only 20\\u20AC!";  // U+20AC is EURO sign.\n    DWORD written;\n    \n    if(type == FILE_TYPE_DISK)\n        WriteFile(h, s, sizeof(s) - sizeof(WCHAR) /* don\'t send the null */, &written, nullptr);\n    else\n        WriteConsoleW(h, s, sizeof(s) / sizeof(WCHAR) - 1, &written, nullptr);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出到控制台:

\n
Only 20\xe2\x82\xac!\n
Run Code Online (Sandbox Code Playgroud)\n

输出重定向到out.txt:\nout.txt 的十六进制转储

\n

  • @PeterCordes 事实并非如此。`WriteConsoleW` 绕过代码页转换,因为 Windows 内部是 UTF-16。`WriteFile`(如 `WriteConsoleA`,顺便说一句)只是“发送字节”,控制台在当前代码页中解释这些字节并转换为相应的 Unicode。UTF-8至少可以处理所有的Unicode。事实上,如果我运行测试代码并重定向到一个文件,该文件包含发送的字节,它们是 UTF-16LE 编码的文本,它只是将这些字节转换为当前代码页的控制台(我的代码页上为 437)系统)。 (2认同)