【问题标题】:C++ Reading UTF-8 file OK, converting to UTF-16 OK, writing back to file NOT OKC++ 读取 UTF-8 文件正常,转换为 UTF-16 正常,写回文件不正常
【发布时间】:2013-09-11 19:11:31
【问题描述】:

我正在使用以下代码读取我在文本编辑器 (Notepad++) 中创建的文本 (.xml) 文件,将我从中读取的 UTF-8 文本转换为 UTF-16,以便 Windows API 运行可以使用它,然后将此 UTF-16 编码的文本写回第二个文件。

我的问题是,当我在 Notepad++ 中打开输出文件时,无论我要求文本编辑器使用什么编码,我都没有得到我期望看到的内容。文件中几乎每个字符之前都有空字符。我假设在将 UTF-16 写入输出文件时我做错了什么,或者 Notepad++ 正在读取为单字节字符。

有什么想法吗?代码如下:

#define UNICODE

// includes...

int main( int argc, char * argv[] )
{
    FILE * pzInFile,
         * pzOutFile;

    try
    {
        char   sUtf8[8192];
        char * pcDst = sUtf8;

        wchar_t wsUtf16[8192];

        _wfopen_s( & pzInFile, L"../config-sample.xml", L"r" );
        _wfopen_s( & pzOutFile, L"../config-sample2.xml", L"w+" );

        if( pzInFile && pzOutFile )
        {
            size_t uiRead;

            while( uiRead = fread_s( pcDst, sizeof( sUtf8 ), 1, 1, pzInFile ) )
            {
                pcDst += uiRead;
            }

            size_t uiLen = pcDst - sUtf8;

            sUtf8[uiLen] = 0;

            MultiByteToWideChar( CP_UTF8, 0, sUtf8, 8192, wsUtf16, 8192 ); // UTF-8 to UTF-16

            fwrite( wsUtf16, 1, uiLen, pzOutFile );
        }
        else
        {
            throw L"Failed to open file";
        }
    }
    catch( const wchar_t * pwsMsg )
    {
        ::MessageBox( NULL, pwsMsg, L"Error", MB_OK | MB_TOPMOST | MB_SETFOREGROUND );
    }

    if( pzInFile )
    {
        fclose( pzInFile );
        pzInFile = 0;
    }
    if( pzOutFile )
    {
        fclose( pzOutFile );
        pzOutFile = 0;
    }

    return 0;
}

【问题讨论】:

  • 听起来 Notepad++ 实际上并没有“理解”UTF-16 字符(或者没有正确“检测”它)。

标签: c++ c windows file unicode


【解决方案1】:

我已修改您的代码以修复一些错误。 Notepad++ 在有或没有 BOM 的情况下都能正确显示输出文件,因此它的编码检测例程似乎很合理。

主要问题是:

  • 输出文件(至少)需要以二进制模式打开,否则带有0A 的两字节UTF-16 字符将在Windows 上转换为0D 0A
  • 我简化了文件读取以在一次读取中填充缓冲区,并考虑了要添加的空终止。
  • MultibyteToWideChar 返回转换的字符数,我使用 -1 作为输入缓冲区大小,因为它是以空值结尾的。
  • fwrite 需要使用转换后的字符串的正确字节数写入转换后的字符(减去 null)。在 UTF-16 中,一个字符是两个字节。

至于您的文件损坏,最初 fwrite 正在写入等于 UTF-8 字符串长度的字节。因为那是 1-3 个字符,所以我得到的原始输出文件长度是 39 个字节......对于 UTF-16 文件来说是不可能的,所以 Notepad++ 的编码启发式可能没有检测到 UTF-16。由于您没有提供示例数据,我编造了一些数据,而我的 Notepad++ 仍然检测到 UTF-16,因此启发式方法也可能会根据数据进行猜测。

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

int main()
{
    FILE* pzInFile;
    FILE* pzOutFile;

    try
    {
        char    sUtf8[8192];
        wchar_t wsUtf16[8192];

        _wfopen_s(& pzInFile, L"config-sample.xml", L"r");
        _wfopen_s(& pzOutFile, L"config-sample2.xml", L"w+b");

        if(pzInFile && pzOutFile)
        {
            size_t uiRead = fread_s(sUtf8, sizeof(sUtf8), 1, sizeof(sUtf8) - 1, pzInFile);
            sUtf8[uiRead] = 0;

            int wlen = MultiByteToWideChar(CP_UTF8, 0, sUtf8, -1, wsUtf16, 8192);   // UTF-8 to UTF-16

            fwrite(wsUtf16, 1, (wlen-1) * sizeof(wchar_t), pzOutFile);
        }
        else
        {
            throw L"Failed to open file";
        }
    }
    catch(const wchar_t* pwsMsg)
    {
        ::MessageBox(NULL, pwsMsg, L"Error", MB_OK | MB_TOPMOST | MB_SETFOREGROUND);
    }

    if(pzInFile)
    {
        fclose(pzInFile);
        pzInFile = 0;
    }
    if(pzOutFile)
    {
        fclose(pzOutFile);
        pzOutFile = 0;
    }

    return 0;
}

我的数据文件包含:

<data>αßΓπΣσµτΦΘΩδ</data>

这是输入和输出文件的十六进制转储。请注意,在输入时,由于我将输入文件以文本模式打开,0D0A CR-LF 组合被转换为 0A,因此只有换行符最终出现在二进制模式输出文件中。我留下来说明 Windows 文本与二进制问题。您可能应该以二进制模式打开两者。

input:  3C646174613ECEB1C39FCE93CF80CEA3CF83C2B5CF84CEA6CE98CEA9CEB43C2F646174613E0D0A0D0A
output: 3C0064006100740061003E00B103DF009303C003A303C303B500C403A6039803A903B4033C002F0064006100740061003E000A000A00

【讨论】:

    【解决方案2】:

    对于 UTF-16 编码的文本文件,您应该在文件开头写出 byte order mark (BOM),以便任何读取文件的程序都知道文件是小端 (UTF-16LE) 还是大端(UTF-16BE)。如果一个文本文件不是以FF FEFE FF 这两个字节开头,那么大多数程序会假定该文件是使用单字节编码(例如UTF-8 或Windows-1252)编写的。

    一种方法是这样的:

    wchar_t bom = 0xFEFF;
    fwrite(&bom, 1, sizeof(bom), pzOutFile);
    fwrite(wsUtf16, 1, uiLen, pzOutFile);
    

    【讨论】:

    • 感谢您的回答。有一个改进:到处都没有空字符,但不是一半的字符是亚洲标志,所以我想还有另一件事要做吗?我也尝试过 FFFE,但结果也很糟糕。顺便说一句,使用fwrite(&amp;bom, 1, sizeof(bom), pzOutFile); 而不是fwrite(&amp;bom, sizeof(bom), 1, pzOutFile); 有什么不同吗?
    • 或者可能是因为我让 Notepad++ 读取为 UCS-2,我认为它是 UTF-16?
    • @Virus721:它们有细微的不同,尤其是对于亚洲字符。
    【解决方案3】:

    您的基本问题是您将 UTF-16 写入一个被读取为 UTF-8 的文件 - 因此每个字符之前都有空字符。您只是想写回您的 UTF-16 转换为 UTF-8。

    【讨论】:

      【解决方案4】:

      我不确定 Notepad++ 检测 UTF-16 的逻辑是什么(如果可以的话),但很像在实际 UTF-16 内容之前需要0xFFEF 的“字节序标记” .

      【讨论】:

        猜你喜欢
        • 2014-03-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-01
        • 1970-01-01
        • 2023-03-16
        • 2014-03-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多