3

使用std::wstring我的方式MultiByteToWideChar

std::wstring widen(const std::string &in)
{
    int len = MultiByteToWideChar(CP_UTF8, 0, &in[0], -1, NULL, 0);
    std::wstring out(len, 0);
    MultiByteToWideChar(CP_UTF8, 0, &in[0], -1, &out[0], len);
    return out;
}
4

4 回答 4

5

您第一次调用 有一个问题MultiByteToWideChar:不能保证字符序列以零结尾(尽管实际上通常是这样)。将该行更改为

int len = MultiByteToWideChar(CP_UTF8, 0, in.c_str(), -1, NULL, 0);

你应该是安全的。即使MultiByteToWideChar失败并返回 0,这也是通过len在第二次调用中作为最终参数传递来解释的MultiByteToWideChar

话虽如此,它是安全的,因为它不会崩溃或损坏内存。然而,还有一个问题:除非输入字符串导致MultiByteToWideChar失败,否则返回的字符串将声称它size()比它应该的大一个字符。我建议按如下方式更改代码:

std::wstring widen(std::string const &in)
{
    std::wstring out{};

    if (in.length() > 0)
    {
        // Calculate target buffer size (not including the zero terminator).
        int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
                                      in.c_str(), in.size(), NULL, 0);
        if ( len == 0 )
        {
            throw std::runtime_error("Invalid character sequence.");
        }

        out.resize(len);
        // No error checking. We already know, that the conversion will succeed.
        MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
                            in.c_str(), in.size(), &out[0], out.size());
                            // Use out.data() in place of &out[0] for C++17
    }

    return out;
}

此实现解决了以下问题:

  • 如果输入序列不是有效的 UTF-8,它会通过传递MB_ERR_INVALID_CHARS标志来报告错误。
  • 通过抛出异常来报告错误。这使得区分转换错误和成功调用(返回零大小的字符串)成为可能。(注意:std::wstringc'tor 已经在失败的情况下抛出异常。对于其他错误抛出异常会感觉不自然。 )
  • 该实现正确处理包含嵌入NUL字符的输入。这很少使用,但是当它使用时(例如,在组成OPENFILENAMElpstrFilter成员时),它不会因此(默默地)失败。
  • 它不会过度分配返回值的容器存储。如果cbMultiByte参数-1在对 的调用中设置为MultiByteToWideChar,则返回的长度确实包含零终止符的空间。但是,此字符由std::string实现拥有,而不是要转换的字符序列的一部分。
  • 与上一个要点相关,此实现不转换零终止符。原始代码确实如此,NULc_str()调用成员时,返回的字符串在字符串末尾产生 2 个字符。
于 2013-01-06T17:33:36.740 回答
5

如果你问它会工作,可能。这是正确的吗?

  1. 你应该使用in.c_str()而不是&in[0]
  2. MultiByteToWideChar您应该至少第一次检查返回值。
  3. MultiByteToWideChar以 (-1) 长度调用,如果成功,将包括一个零终止符(即,成功时它总是返回 >= 1)。的长度构造函数std::wstring不需要这个。std::wstring(5,0)将为六个宽字符分配空间;5+零项。所以从技术上讲,你分配了一个太多的宽字符。

MultiByteToWideChardocs oncbMultiByte和 -1 开始:

如果此参数为 -1,则该函数处理整个输入字符串,包括终止空字符。因此,生成的 Unicode 字符串有一个终止空字符,并且函数返回的长度包括该字符。

于 2013-01-06T17:40:20.317 回答
0

不,因为std::wstring不能保证 a 将其数据存储在连续的内存块中(尽管它很可能在您的实现中这样做)。改用 a std::vector<wchar_t>

于 2013-01-06T17:37:02.890 回答
0

其他答案很好,但我想根据我自己对同一问题的研究为未来的访问者添加一些额外的信息。

  1. Microsoft 开发人员 Larry Osterman 有一篇很好的博客文章描述了这样一个函数,其中包含关于返回代码检查和 NRVO(命名返回值优化)的非常好的观点。如果它仍然可用,您应该阅读该帖子以进行讨论。我包括他的最终代码,以防万一帖子丢失。

    
    std::wstring UnicodeStringFromAnsiString(_In_ const std::string &ansiString)
    {
        std::wstring returnValue;
        auto wideCharSize = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansiString.c_str(), -1, nullptr, 0);
        if (wideCharSize == 0)
        {
            return returnValue;
        }
        returnValue.resize(wideCharSize);
        wideCharSize = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansiString.c_str(), -1, &returnValue[0], wideCharSize);
        if (wideCharSize == 0)
        {
            returnValue.resize(0);
            return returnValue;
        }
        returnValue.resize(wideCharSize-1);
        return returnValue;
    }
    

在我自己的使用中,我能够添加博客评论中提到的优化,并且 ANSI 字符串长度不需要 -1。

  1. C++17(第 21.3.1.7.1 节)记录了一个新添加的非常量data()方法,应该使用它来代替&in[0]获取可变指针。

    charT* data() noexcept;

  2. STL 拥有结果中的尾随\0c_str()因此请小心处理字符串大小。

于 2016-08-24T12:19:45.013 回答