【问题标题】:Read unicode characters from file in C从C中的文件中读取unicode字符
【发布时间】:2019-03-18 17:41:28
【问题描述】:

我正在尝试从 .csv 文件中读取 UTF-8 字符串,然后将其写入控制台。

a.csv 内容:

Gijón

在对该主题进行了一整天的研究后,我发现执行此类操作的所谓正确方法应该类似于以下内容:

int main(int argc, char *argv[])
{
    char *locale = setlocale(LC_ALL, "");
    printf("locale: %s\n", locale);

    const int MAX_LINE_SIZE = 1024;
    char line[MAX_LINE_SIZE];
    wchar_t wline[MAX_LINE_SIZE];

    // Attempt 0: no special handling
    FILE* stream = fopen("a.csv", "r");
    fgets(line, MAX_LINE_SIZE, stream);
    printf("%s\n", line); // Expected to print "Gijón", prints "Gijón"
    fclose(stream);

    // Attempt 1: mbstowcs
    mbstowcs(wline, line, MAX_LINE_SIZE);
    wprintf(L"%ls\n", wline); // Expected to print "Gijón", prints "Gijón"

    // Attempt 2: fgetws
    stream = fopen("a.csv", "r");
    fgetws(wline, MAX_LINE_SIZE, stream);
    wprintf(L"%ls\n", wline); // Expected to print "Gijón", prints "Gijón"
    fclose(stream);

    // Attempt 3: _wfopen
    stream = _wfopen(L"a.csv", L"rb");
    fgetws(wline, MAX_LINE_SIZE, stream);
    wprintf(L"%ls\n", wline); // Expected to print "Gijón", prints ""
    fclose(stream);

    // Printing command line parameter
    mbstowcs(wline, argv[1], MAX_LINE_SIZE);
    wprintf(L"%ls\n", wline); // Properly prints "Gijón"
}

但是运行这个程序会导致:

.\myprogram.exe Gijón
locale: Spanish_Spain.1252
Gijón
Gijón

Gijón

我不认为这是控制台本身的问题,因为argv[1] 转换工作正常。

我错过了什么?

【问题讨论】:

  • 适用于 Windows 还是 Linux/Mac?
  • 我使用的是 Windows
  • 什么是_wfopen?这不是标准 C 的一部分,不是吗?它似乎是特定于 Microsoft 的。
  • 我在这里找到了它:stackoverflow.com/questions/9523951/… 无论如何它似乎对我不起作用,只是拼命尝试让它起作用
  • 仅供参考,在 Mac 上,我能够运行您的整个程序 除了 用于特定于 Windows 的_wfopen,我看到它每次都打印Gijón。我希望 Windows 专家能在此指点一下。我确实知道 Mac 终端程序默认是 UTF-8,所以我怀疑但不能确定 Windows 中有一些控制台设置需要检查。抱歉,目前这里无法访问 Windows。

标签: c windows unicode character-encoding console


【解决方案1】:

wchar_t 和宽字符函数(wfopen 等)主要在 Windows 中用于处理 UTF16 编码的 Unicode。

UTF8 使用 char 和相同的 ASCII 兼容 C 函数(fopen 等)要读取 UTF8,您可以对 ASCII 使用相同的 C 函数。

Windows 不完全支持读取和显示 UTF8,因此您必须在 UTF8 和 UTF16 之间进行转换才能正确显示文本。 Windows 10 确实支持控制台 Windows 的 UTF8,请参阅相关主题。

#include <stdio.h>
#include <windows.h>

int main(void)
{
    const char* filename = "a.csv";
    FILE* fp = fopen(filename, "r");
    char buf[1000];
    fgets(buf, sizeof(buf), fp);

    if(strlen(buf) > 2)
        if(strncmp(buf, "\xFF\xFE", 2) == 0)
        {
            printf("UTF16-LE\n");
            fclose(fp);
            fp = fopen(filename, "rb");
            wchar_t wbuf[1000] = { 0 };
            fgets((char*)wbuf, sizeof(buf), fp);
            MessageBoxW(0, wbuf, L"UTF16-LE", 0);
            return 0;
        }

    if(strlen(buf) > 3)
        if(strncmp(buf, "\xEF\xBB\xBF", 3) == 0)
            printf("UTF8 with BOM\n");

    //assume UTF8 and convert to UTF16:
    int size = MultiByteToWideChar(CP_UTF8, 0, buf, -1, NULL, 0);
    wchar_t *utf16 = malloc((size + 1) * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, buf, -1, utf16, size);

    MessageBoxA(0, buf, "ANSI", 0);
    MessageBoxW(0, utf16, L"UTF8 converted", 0);
    return 0;
}

如果源文件是 UTF8,那么您基本上将其视为 ASCII。请注意 strtok 之类的函数,它不能处理 ASCII 范围之外的输入字符。唯一的其他复杂情况是当您尝试在 Windows 中打印它时。将下面的示例与自定义 printf 函数一起使用:

void printf_utf8(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    int len = _vscprintf(format, args) + 1; 
    char *buf = malloc(len);
    vsprintf(buf, format, args);

    //convert to UTF16 and print
    int wbuf_size = MultiByteToWideChar(CP_UTF8, 0, buf, -1, NULL, 0);
    wchar_t *wbuf = malloc((wbuf_size + 1) * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, wbuf_size);

    DWORD temp;
    HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    WriteConsoleW(h, wbuf, wcslen(wbuf), &temp, 0);

    free(wbuf);
    free(buf);
}

int main(void)
{
    FILE* fp = fopen("a.csv", "r");
    if(!fp)
        return 0;
    char buf[1000];
    fgets(buf, sizeof(buf), fp);
    printf_utf8("Test %s %d\n", buf, 123);
    return 0;
}

【讨论】:

  • 根据我正在使用的文本编辑器(notepad++),文件以 UTF8 编码。此代码使弹出“UTF8 转换”以正确显示“Gijón”(+1)。我在记事本中发现了一个选项,可以将文件中的编码转换为 ANSI(其中也有一个 ó 字符),并使我的问题代码正确显示“Gijón”。但我的目的是能够处理 UTF8 编码的文件。
  • 问。 MultiByteToWideChar 是特定于 Windows 平台的。多平台解决方案会是什么样子? linux中是否需要任何特殊处理?
  • 第二季度。 strncmp 如何识别文件的编码? (如果这可能是一个不同的 SO 问题,那么在此处的某个地方有一个指向它的链接会很好)。
  • 当您运行该程序时,它是否打印了"UTF8 with BOM"? UTF8 文件有时有 BOM,有时没有。如果它有 BOM,则跳过前 3 个字节。对于 Linux/Mac,只需使用普通的 C 函数。 Linux 可以很好地读取/显示 UTF8。对于 Windows,它是一样的,除了当你想向用户显示它时,你必须将它转换为 UTF16。
  • 从 Windows 8 开始,控制台允许通过 WritFileWriteConsoleA 写入 UTF-8。但即使是 Windows 10(1803 版)也不支持通过 ReadFileReadConsoleA 从控制台读取 UTF-8。它仅限于 7 位 ASCII。它无法使用非 ASCII UTF-8 处理每个代码 2-4 字节的可变大小编码,因此它用 ASCII NUL 替换非 ASCII 字符(即'\0')。也许这在 1809 版本中已修复。在 Windows 10 之前情况更糟,在这种情况下,尝试将非 ASCII 字符读取为 UTF-8 会导致空读取,这通常被视为文件结尾。
【解决方案2】:

我认为您必须将宽字符转换为 1252 编码。 1252 编码是 8bit/char 编码,仅支持 unicode 字符的一小部分。 也许有可用的转换函数/库。但是自己写似乎并不太复杂(big switch/case 子句)。

【讨论】:

    猜你喜欢
    • 2012-02-04
    • 2018-06-22
    • 1970-01-01
    • 1970-01-01
    • 2012-11-21
    • 1970-01-01
    • 2011-01-19
    • 2016-10-16
    相关资源
    最近更新 更多