MultiByteToWideChar使用垃圾终止输出缓冲区,但报告没有错误。为什么?

时间:2012-10-21 15:23:57

标签: c++ c winapi unicode visual-studio-2012

前几天开发程序时,我不得不将ASCII字符串转换为Unicode字符串。顺便说一下,我正在使用Visual Studio 2012在Windows上工作。我注意到Win32函数MultiByteToWideChar的一些奇怪的行为,我无法理清。我在下面写了一些测试代码:

int main()
{
    /* Create const test string */
    char str[] = "test string";

    /* Create empty wchar_t buffer to hold Unicode form of above string, and initialize (zero) it */
    wchar_t *buffer = (wchar_t*) LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t) * strlen(str));

    /* Convert str to Unicode and store in buffer */
    int result = MultiByteToWideChar(CP_UTF8, NULL, str, strlen(str), buffer, strlen(str));
    if (result == 0)
        printf("GetLastError result: %d\n", GetLastError());

    /* Print MultiByteToWideChar result, str's length, and buffer's length */
    printf_s(
        "MultiByteToWideChar result: %d\n"
        "'str' length: %d\n"
        "'buffer' length: %d\n",
        result, strlen(str), wcslen(buffer));

    /* Create a message box to display the Unicode string */
    MessageBoxW(NULL, buffer, L"'buffer' contents", MB_OK);

    /* Also write buffer to file, raw */
    FILE *stream = NULL;
    fopen_s(&stream, "c:\\test.dat", "wb");
    fwrite(buffer, sizeof(wchar_t), wcslen(buffer), stream);
    fclose(stream);

    return 0;
}

正如您所看到的,它只需要一个普通的字符串,创建一个缓冲区来存储Unicode字符串,将转换后的Unicode字符串放入缓冲区,并向我显示一些结果,同时将缓冲区写入文件。 / p>

输出:

MultiByteToWideChar result: 11
'str' length: 11
'buffer' length: 16

已经很奇怪了。该函数正在处理C字符串中正确数量的字符,但wcslen报告输出缓冲区长于C字符串!我很确定我也正确分配了缓冲区。

我尝试使用不同大小的字符串长度,但最后总是有垃圾,而wcslen总是将缓冲区的长度报告为4的倍数。

最后,对于这个特定字符串("test string"),这是打印到文件的原始缓冲区:

74 00 65 00 73 00 74 00 20 00 73 00 74 00 72 00   t.e.s.t. .s.t.r.
69 00 6E 00 67 00 AB AB AB AB AB AB AB AB EE FE   i.n.g...........

(那是32个字节,或16个Unicode字符。)

最后10个字节是5个字符;四个U+ABAB和一个U+FEEE,对我来说毫无意义。

以不同的金额,每次 我会尝试转换字符串。

我有点想法。任何人吗?

提前致谢!

3 个答案:

答案 0 :(得分:5)

/* Create empty wchar_t buffer to hold Unicode form of above string, and initialize (zero) it */
wchar_t *buffer = (wchar_t*) LocalAlloc(LMEM_ZEROINIT, sizeof(wchar_t) * strlen(str));

这确实是问题的起点。 strlen(str)的值没有意义,特别是当输入字符串以utf-8编码时。你偶然会侥幸逃脱它,因为通常会创建一个太长的缓冲区,而不是计算一个一个错误。

但你也可以通过正确的方式轻松避免这个错误。您必须调用函数两次。第一次,为最后一个参数(cchWideChar)传递0。该函数返回缓冲区所需的大小(字符,而不是字节)。现在,这足以在第二次调用函数时分配缓冲区传递正确的值。

答案 1 :(得分:4)

(转换评论回答)

您需要在长度中包含尾随空字符(传递strlen(str) + 1而不是strlen(str))。此外,buffer是一个元素太短 - 它还需要留下空尾字符的空间。

答案 2 :(得分:4)

正如其他人所评论的那样,你基本上没有正确地处理空终结符而误用MultiByteToWideChar()wcslen()。如果在调用MultiByteToWideChar()时不包含空终止符,则它不会输出空终止符。

请改为尝试:

int main() 
{ 
    /* Create const test string */ 
    char str[] = "test string"; 
    int strLen = strlen(str);

    WCHAR *buffer = NULL;
    int bufLen = 0;

    /* Calculate buffer size */ 
    int result = MultiByteToWideChar(CP_UTF8, NULL, str, strLen, NULL, 0); 
    if (result > 0)
    {
        /* Create buffer to hold Unicode form of above string */ 
        buffer = (WCHAR*) LocalAlloc(LPTR, sizeof(WCHAR) * (result+1)); 
        if (buffer != NULL)
        { 
            /* Convert str to Unicode and store in buffer */ 
            bufLen = result; 
            result = MultiByteToWideChar(CP_UTF8, NULL, str, strLen+1, buffer, bufLen); 
        }
    }

    if ((!buffer) || (result == 0))
        printf("GetLastError result: %d\n", GetLastError());          

    /* Print MultiByteToWideChar result, str's length, and buffer's length */ 
    printf_s( 
        "MultiByteToWideChar result: %d\n" 
        "'str' length: %d\n" 
        "'buffer' length: %d\n", 
        result, strLen, bufLen); 

    /* Create a message box to display the Unicode string */ 
    MessageBoxW(NULL, buffer, L"'buffer' contents", MB_OK); 

    /* Also write buffer to file, raw */ 
    FILE *stream = NULL; 
    errno_t err = fopen_s(&stream, "c:\\test.dat", "wb");
    if (err == 0)
    { 
        fwrite(buffer, sizeof(WCHAR), bufLen, stream); 
        fclose(stream); 
    }
    else
        printf("Errno result: %d\n", err);

    if (buffer)
        LocalFree(buffer);

    return 0; 
} 

由于您使用的是C ++,因此可以使用std::stringstd:wstring来简化内存管理

int main() 
{ 
    /* Create const test string */ 
    std::string str = "test string"; 
    std::wstring buffer;

    /* Calculate buffer size */ 
    int result = MultiByteToWideChar(CP_UTF8, NULL, str.c_str(), str.length(), NULL, 0); 
    if (result > 0)
    {
        /* Allocate buffer to hold Unicode form of above string */ 
        buffer.resize(result); 

        /* Convert str to Unicode and store in buffer */ 
        result = MultiByteToWideChar(CP_UTF8, NULL, str.c_str(), str.length(), &buffer[0], result); 
    }

    if (result == 0)
        printf("GetLastError result: %d\n", GetLastError());          

    /* Print MultiByteToWideChar result, str's length, and buffer's length */ 
    printf_s( 
        "MultiByteToWideChar result: %d\n" 
        "'str' length: %d\n" 
        "'buffer' length: %d\n", 
        result, str.length(), buffer.length()); 

    /* Create a message box to display the Unicode string */ 
    MessageBoxW(NULL, buffer.c_str(), L"'buffer' contents", MB_OK); 

    /* Also write buffer to file, raw */ 
    FILE *stream = NULL; 
    errno_t err = fopen_s(&stream, "c:\\test.dat", "wb");
    if (err == 0)
    { 
        fwrite(buffer.data(), sizeof(std::wstring::value_type), buffer.length(), stream); 
        fclose(stream); 
    }
    else
        printf("Errno result: %d\n", err);

    return 0; 
}