在 Windows 上使用 C 中的 WideCharToMultiByte 将 UTF-16 转换为 UTF-8

标签 c winapi unicode utf-8 utf-16

我正在尝试转换 Windows wchar_t[]到 UTF-8 编码 char[]这样就可以调用 WriteFile将产生UTF-8编码的文件。我有以下代码:

#include <windows.h>
#include <fileapi.h>
#include <stringapiset.h>

int main() {
    HANDLE file = CreateFileW(L"test.txt", GENERIC_ALL, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    const wchar_t source[] = L"hello";
    char buffer[100];
    WideCharToMultiByte(CP_UTF8, 0, source, sizeof(source)/sizeof(source[0]), buffer, sizeof(buffer)/sizeof(buffer[0]), NULL, NULL);
    WriteFile(file, buffer, sizeof(buffer), NULL, NULL);
    return CloseHandle(file);
}

这会生成一个文件,其中包含:“hello”,但后面还包含大量垃圾。 enter image description here

与此相关的一些事情让我认为问题不仅仅是简单地将多余的字符转储到 buffer 中。并且转换没有正确发生,所以我更改了 source正文如下:

const wchar_t source[] = L"привет";

这次得到了以下垃圾:

enter image description here

那么,也许它会感到困惑,因为它正在寻找一个空终止符,但没有找到一个,即使指定了长度?所以我再次更改源字符串:

const wchar_t source[] = L"hello\n";

并得到以下垃圾:

enter image description here

我对 WinAPI 相当陌生,而且主要不是 C 开发人员,所以我确信我错过了一些东西,我只是不知道还能尝试什么。

编辑: 按照 RbMm 的建议删除了多余的垃圾,因此英语可以正确打印。然而,俄语仍然是垃圾,只是更短的垃圾。与 zett42 的评论相反,我肯定使用的是 UTF-8 文本编辑器。

enter image description here

UTF-8 doesn't need a BOM ,但无论如何添加一个都会产生:

enter image description here

这很奇怪。我期望相同的文本具有稍大的二进制大小。相反,什么也没有。

编辑:

由于有些人热衷于坚持认为我正在使用写字板,因此写字板的外观如下

enter image description here

我显然没有使用写字板。我正在使用 VS Code,尽管无论是在 VS Code、Visual Studio、Notepad 还是 Notepad++ 中打开,垃圾都是相同的。

编辑:

这是俄语输出的十六进制转储:

enter image description here

最佳答案

更新 3:十六进制输出表明源文件在编译过程中的某个地方被误解了。使用 Windows 代码页 1252,而不是使用 UTF-8,这意味着该字符串在编译的程序中具有错误的编码。因此,输出文件中存储的字节序列是 C3 90 C2 Bf C3 91 E2 82 AC C3 90 C2 B8 90 C2 B2 C3 90 C2 B5 C3 91 E2 80 9A 而不是正确的 D0 BF D1 80 D0 B8 D0 B2 D0 B5 D1 82.

如何解决这个问题取决于工具链。 MSVC 具有 /utf-8用于设置源和执行字符集的标志。您可能会认为这是相当多余的,因为您已经将源文件保存为 UTF-8?事实证明,写字板并不是唯一需要 BOM 来检测 UTF-8 的软件。以下文档摘录解释了整个编码问题的原因。

By default, Visual Studio detects a byte-order mark to determine if the source file is in an encoded Unicode format, for example, UTF-16 or UTF-8. If no byte-order mark is found, it assumes the source file is encoded using the current user code page, unless you have specified a code page by using /utf-8 or the /source-charset option.

在 Visual Studio 17 中,您还可以通过在配置属性 > 常规 > 项目默认值中设置字符集来配置字符集。如果您使用 cmake,您可能不会遇到此问题,因为它可以开箱即用地正确配置所有内容。

更新 2: 有些编辑器可能无法从这样的短字节序列中推断出内容是 UTF-8,这将导致您看到的乱码输出。您可以在文件开头添加 UTF-8 字节顺序标记 (BOM) 来帮助这些编辑者,尽管这不被认为是最佳实践,因为它会合并元数据和内容,破坏 ASCII 向后兼容性,并且可以正确检测到 UTF-8没有它。大多数是旧版软件,例如 Microsoft 的写字板,需要 BOM 将文件解释为 UTF-8。

if (WriteFile(file, "\xef\xbb\xbf", 3, NULL, NULL) == 0) { goto error; }

更新:带有一些基本错误处理的代码:

#include <windows.h>
#include <fileapi.h>
#include <stringapiset.h>

int main() {
    int ret_val = -1;

    const wchar_t source[] = L"привет";

    HANDLE file = CreateFileW(L"test.txt", GENERIC_ALL, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (file == INVALID_HANDLE_VALUE) { goto error_0; }

    size_t required_size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL);

    if (required_size == 0) { goto error_0; }

    char *buffer = calloc(required_size, sizeof(char));

    if (buffer == NULL) { goto error_0; }

    if (WideCharToMultiByte(CP_UTF8, 0, source, -1, buffer, required_size, NULL, NULL) == 0) { goto error_1; }

    if (WriteFile(file, buffer, required_size - 1, NULL, NULL) == 0) { goto error_1; }

    if (CloseHandle(file) == 0) { goto error_1; }

    ret_val = 0;

error_1:
    free(buffer);

error_0:
    return ret_val;
}

: 您可以执行以下操作,这将很好地创建文件。对 WideCharToMultiByte 的第一次调用用于确定存储 UTF-8 字符串所需的字节数。确保将源文件保存为 UTF-8,否则源字符串将无法在源文件中正确编码。

以下代码只是一个快速而肮脏的示例,缺乏严格的错误处理。

#include <windows.h>
#include <fileapi.h>
#include <stringapiset.h>

int main() {
    HANDLE file = CreateFileW(L"test.txt", GENERIC_ALL, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    const wchar_t source[] = L"привет";

    size_t required_size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL);

    char *buffer = (char *) calloc(required_size, sizeof(char));

    WideCharToMultiByte(CP_UTF8, 0, source, -1, buffer, required_size, NULL, NULL);
    WriteFile(file, buffer, required_size - 1, NULL, NULL);
    free(buffer);
    return CloseHandle(file);
}

关于在 Windows 上使用 C 中的 WideCharToMultiByte 将 UTF-16 转换为 UTF-8,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57134511/

相关文章:

c++ - 将单个字符串/字符输入串行监视器

java - 如何在 Java 上的 Windows 中启动交互式进程?

python - 可怕的 python 编码错误,如何阻止它们?

c - 分布式网络多客户端

使用字符串将二进制转换为八进制

c++ - 如何在win32按钮上显示颜色与背景色相同的透明png图像文件

c++ - 有效地列出目录中的所有子目录

javascript - 从 javascript 字符串中剥离 U+10000-U+10FFFF

c++ - 如何在 Verifone VX675 中使用 unicode 字体

c - 在嵌入式 MCU 应用程序中,在 for 循环中使用 uint_fast16_t 还是 size_t 更好?