0

我正在使用以下代码从我在文本编辑器(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;
}
4

4 回答 4

3

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

主要问题是:

  • 输出文件(至少)需要以二进制模式打开,否则其中包含的两字节 UTF-16 字符0A将在 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>

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

input:  3C646174613ECEB1C39FCE93CF80CEA3CF83C2B5CF84CEA6CE98CEA9CEB43C2F646174613E0D0A0D0A
output: 3C0064006100740061003E00B103DF009303C003A303C303B500C403A6039803A903B4033C002F0064006100740061003E000A000A00
于 2013-09-08T00:22:09.687 回答
2

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

一种方法是这样的:

wchar_t bom = 0xFEFF;
fwrite(&bom, 1, sizeof(bom), pzOutFile);
fwrite(wsUtf16, 1, uiLen, pzOutFile);
于 2013-09-07T23:29:40.820 回答
0

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

于 2013-09-07T23:28:13.700 回答
0

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

于 2013-09-07T23:30:39.870 回答