如何正确使用WideCharToMultiByte函数

80

我已经阅读了关于WideCharToMultiByte的文档,但我卡在这个参数上:

lpMultiByteStr
[out] Pointer to a buffer that receives the converted string.

我不太确定如何正确初始化变量并将其传递给函数。


31
你似乎经常提出问题却不接受回答,是否有什么原因?在这些网站上,通常会通过反馈来奖励给出好答案的人,以认可他们为回答你的问题所付出的时间。下面已经有几个非常好的答案了...(轻推) - Assad Ebrahim
4个回答

154

这里有一对函数(基于Brian Bondy的示例),使用WideCharToMultiByte和MultiByteToWideChar来转换std::wstring和std::string之间的数据,以utf8编码,以避免丢失任何数据。

// Convert a wide Unicode string to an UTF8 string
std::string utf8_encode(const std::wstring &wstr)
{
    if( wstr.empty() ) return std::string();
    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string strTo( size_needed, 0 );
    WideCharToMultiByte                  (CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

// Convert an UTF8 string to a wide Unicode String
std::wstring utf8_decode(const std::string &str)
{
    if( str.empty() ) return std::wstring();
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
    std::wstring wstrTo( size_needed, 0 );
    MultiByteToWideChar                  (CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
    return wstrTo;
}

11
值得注意的是,在 C++11 之前,std::string和std::wstring的内存不一定是连续的。 - Chris_F
7
我严重怀疑已经有过一种商用stl实现不具备连续向量。连续内存在C++规范的初版中不是必需的,这是一个疏忽:http://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/。 - tfinniga
4
@tfinniga之前的评论是关于字符串而不是向量。在C++98中,并没有保证字符串是连续的(这并不是Sutter所提到的结果),尽管所有实际的实现都会使它们连续。请注意,本文仅涉及字符串和其连续性问题,与向量无关。 - user4815162342
5
@Swift c_str()保证返回指向连续缓冲区的指针,但在C++11之前,这不能保证与字符串的内部表示相同。 - Chris_F
3
自从C++17中废除了<codecvt>,类似的事情已经成为了某种强制要求。例如:https://dev59.com/3LTma4cB1Zd3GeqP-Kul。 - kayleeFrye_onDeck
显示剩余5条评论

40

在Brian R. Bondy提供的答案上作详细说明:下面是一个例子,说明为什么你不能简单地将输出缓冲区大小设置为源字符串中宽字符的数量:

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

/* string consisting of several Asian characters */
wchar_t wcsString[] = L"\u9580\u961c\u9640\u963f\u963b\u9644";

int main() 
{

    size_t wcsChars = wcslen( wcsString);

    size_t sizeRequired = WideCharToMultiByte( 950, 0, wcsString, -1, 
                                               NULL, 0,  NULL, NULL);

    printf( "Wide chars in wcsString: %u\n", wcsChars);
    printf( "Bytes required for CP950 encoding (excluding NUL terminator): %u\n",
             sizeRequired-1);

    sizeRequired = WideCharToMultiByte( CP_UTF8, 0, wcsString, -1,
                                        NULL, 0,  NULL, NULL);
    printf( "Bytes required for UTF8 encoding (excluding NUL terminator): %u\n",
             sizeRequired-1);
}

输出结果为:

Wide chars in wcsString: 6
Bytes required for CP950 encoding (excluding NUL terminator): 12
Bytes required for UTF8 encoding (excluding NUL terminator): 18

3
一个关于代码页/编码转换重要但经常被忽视方面的绝佳示例! - Johann Gerell
-1 OP请求帮助lpMultiByteStr参数。此答案并未回答OP的问题,而是与另一个发布的答案无关。 - Error 454
3
2008年时还没有评论功能,只需举报即可。 - Ry-
1
+1 表示不包括 null,返回的 sizeRequired 包括一个 null 的空间,因此 lpMultiByteStr 的适当初始化必须考虑到这一点。 - Les

20

通过创建一个新的 char 数组来使用 lpMultiByteStr [out] 参数。然后,您将该 char 数组传入以获取填充。您只需要初始化字符串的长度+1,以便在转换后获得一个空终止字符串。

下面是一些有用的辅助函数,它们展示了所有参数的用法。

#include <string>

std::string wstrtostr(const std::wstring &wstr)
{
    // Convert a Unicode string to an ASCII string
    std::string strTo;
    char *szTo = new char[wstr.length() + 1];
    szTo[wstr.size()] = '\0';
    WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, szTo, (int)wstr.length(), NULL, NULL);
    strTo = szTo;
    delete[] szTo;
    return strTo;
}

std::wstring strtowstr(const std::string &str)
{
    // Convert an ASCII string to a Unicode String
    std::wstring wstrTo;
    wchar_t *wszTo = new wchar_t[str.length() + 1];
    wszTo[str.size()] = L'\0';
    MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, wszTo, (int)str.length());
    wstrTo = wszTo;
    delete[] wszTo;
    return wstrTo;
}

无论何时在文档中看到一个参数是指向某种类型的指针,并且它们告诉你这是一个输出变量,那么你就需要创建该类型,然后传入一个指向它的指针。函数将使用该指针来填充您的变量。

因此,您可以更好地理解这一点:

//pX is an out parameter, it fills your variable with 10.
void fillXWith10(int *pX)
{
  *pX = 10;
}

int main(int argc, char ** argv)
{
  int X;
  fillXWith10(&X);
  return 0;
}

6
代码应考虑到多字节字符串所需的字节数可能比宽字符串中的字符数更多。单个宽字符在多字节字符串中可能会产生2个或更多字节,这取决于所涉及的编码方式。 - Michael Burr
亚洲字符是一个例子,但实际上取决于用于转换的代码页。在您的示例中,这可能不是问题,因为任何非ANSI字符都将被替换为问号。 - HS.
3
要获取所需的转换大小,调用WideCharToMultiByte并将目标缓冲区的大小设为0。它将返回目标缓冲区大小所需的字节数。 - HS.
有没有可移植的方法来执行这个操作,例如POSIX?WideCharToMultiByte是Windows函数。 - Harvey
当使用gb2312这样的字符集进行工作时,使用此代码会导致字节数或宽字符计数中断。 - bronze man
自2008年以来,Windows已经发生了变化!Vista -> Win 10。Brian Bondy的答案仍然是最清晰、最专注于OP问题的。问题中链接的文档已经移动到这里 - user10186832

1
这里是关于WideCharToMultiByteMultiByteToWideChar的C语言实现。在两种情况下,我都确保在目标缓冲区的末尾添加了一个null字符。

如果在输入字符串长度中显式指定没有终止的null字符,则MultiByteToWideChar不会对输出字符串进行null终止。

并且

如果在输入字符串长度中显式指定没有终止的null字符,则WideCharToMultiByte不会对输出字符串进行null终止。

即使有人指定-1并传入一个以null结尾的字符串,我仍会为另一个null字符分配足够的空间,因为对于我的用例,这并不是问题。
wchar_t* utf8_decode( const char* str, int nbytes ) {    
    int nchars = 0;
    if ( ( nchars = MultiByteToWideChar( CP_UTF8, 
        MB_ERR_INVALID_CHARS, str, nbytes, NULL, 0 ) ) == 0 ) {
        return NULL;
    }

    wchar_t* wstr = NULL;
    if ( !( wstr = malloc( ( ( size_t )nchars + 1 ) * sizeof( wchar_t ) ) ) ) {
        return NULL;
    }

    wstr[ nchars ] = L'\0';
    if ( MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, 
        str, nbytes, wstr, ( size_t )nchars ) == 0 ) {
        free( wstr );
        return NULL;
    }
    return wstr;
} 


char* utf8_encode( const wchar_t* wstr, int nchars ) {
    int nbytes = 0;
    if ( ( nbytes = WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, 
        wstr, nchars, NULL, 0, NULL, NULL ) ) == 0 ) {
        return NULL;
    }

    char* str = NULL;
    if ( !( str = malloc( ( size_t )nbytes + 1 ) ) ) {
        return NULL;
    }

    str[ nbytes ] = '\0';
    if ( WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, 
        wstr, nchars, str, nbytes, NULL, NULL ) == 0 ) {
        free( str );
        return NULL;
    }
    return str;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接