2016 年 9 月

第 31 卷,第 9 期

C++ - 使用 STL 字符串和 Win32 API 实现 Unicode 编码转换

作者 Giovanni Dicanio

Unicode 实际上是一个在现代软件中表示国际文本的标准。根据官方 Unicode 联盟的网站 (bit.ly/1Rtdulx),“Unicode 为每个字符提供了唯一的编号,无论平台、程序和语言如何。” 每个唯一编号称为码位,表示方法通常是“U+”前缀后面跟有唯一编号(十六进制形式)。例如,与字符“C”相关的码位是 U+0043。请注意,Unicode 是一个行业标准,涵盖了全球大多数编写系统,包括象形文字。因此,例如日语汉字象形文字“学”(具有“学习”和“知识”的意思)与码位 U+5B66 相关。当前,Unicode 标准定义的码位数超过 1,114,000 个。

从抽象码位到实际位: UTF-8 和 UTF-16 编码

但是码位是一个抽象的概念。对于程序员,问题是: 如何使用计算机位来精确地表示 Unicode 码位? 此问题的答案直接引导出 Unicode 编码的概念。从根本上说,Unicode 编码是通过具体、明确的定义来表示 Unicode 码位值(以位为单位)。Unicode 标准定义了多个编码,但最重要的是 UTF-8 和 UTF-16,两者均为可变长度编码,能够对几乎所有 Unicode“字符”(甚至码位)进行编码。因此,两种编码之间的转换是无损的: 此过程中不会丢失任何 Unicode 字符。

正如其名,UTF-8 使用 8 位代码单元。它的设计具有两个重要特征。第一个特征是,与 ASCII 向后兼容;即,使用 UTF-8 编码时,每个有效的 ASCII 字符代码均具有相同的字节值。换言之,有效的 ASCII 文本自动成为有效的 UTF-8 编码文本。

第二个特征是,因为以 UTF-8 编码的 Unicode 文本仅是一系列 8 位字节单元,所以不存在字节序复杂的问题。从设计而言,UTF-8 编码(与 UTF-16 不同)是中字节序的。跨具有不同硬件体系结构和不同字节序的计算系统交换文本时,这是一个重要功能。

考虑上述两个 Unicode 字符,大写字母 C(码位 U+0043)是使用单字节 0x43(43 十六进制)以 UTF-8 进行编码的,这完全是与字符 C 相关的 ASCII 代码(根据 UTF-8 与 ASCII 向后兼容)。相反,日语象形文字“学”(码位 U+5B66)是按照三字节序列 0xE5 0xAD 0xA6 以 UTF-8 编码的。

UTF-8 是 Internet 上最常用的 Unicode 编码。根据最近的 W3Techs 统计信息(可在 bit.ly/1UT5EBC 上找到),分析的所有网站中,87% 均在使用 UTF-8。

从根本上说,UTF-16 实际上是一个由支持 Windows Unicode 的 API 使用的标准编码。而且,在许多其他软件系统中,UTF-16 是“本机”Unicode 编码。例如,Qt、Java 和 International Components for Unicode (ICU) 库使用 UTF-16 编码来存储 Unicode 字符串,此处仅举几例。

UTF-16 使用 16 位代码单元。正如 UTF-8,UTF-16 可以对几乎所有 Unicode 码位进行编码。但是,尽管 UTF-8 可以使用一到四个 8 位字节单元对每个有效的 Unicode 码位进行编码,UTF-16 的方式则更为简单。实际上,Unicode 码位是使用一或两个 16 位代码单元以 UTF-16 进行编码的。但是,具有的代码单元比单个字节大,这意味着存在字节序复杂的问题:实际上,同时存在大字节序 UTF-16 和小字节序 UTF-16(而仅存在一个中字节序 UTF-8 编码)。

Unicode 将面的概念定义为一组连续的 65,536 (216) 码位。第一个面定义为面 0 或基本多语言面 (BMP)。几乎所有现代语言的字符和许多符号均位于 BMP 中,且所有这些 BMP 字符均使用单个 16 位代码单元以 UTF-16 表示。

补充字符位于 BMP 之外的其他面中;它们包括表情符号等象形文字符号和埃及象形文字等历史脚本。位于 BMP 之外的这些补充字符使用两个 16 位代码单元(也称为代理项对)以 UTF-16 进行编码。

大写字母 C (U+0043) 是按照单个 16 位代码单元 0x0043 以 UTF-16 进行编码。象形文字“学”(U+5B66) 是按照单个 16 位代码单元 0x5B66 以 UTF-16 进行编码。对于许多 Unicode 字符,在其码位“抽象”表示(如 U+5B66)和其关联十六进制 UTF-16 编码(如 0x5B66 16 位字)之间存在即时、直接的对应关系。

为了有趣一点,我们来看一些象形文字符号。Unicode 字符“snowman”(U+2603 (U+2603) 按三字节序列以 UTF-8 进行编码: 0xE2 0x98 0x83;但是,其 UTF-16 编码是单个 16 位单元 0x2603。Unicode 字符“beer mug”(U+1F37A (U+1F37A) 位于 BMP 之外,按字节序列 0xF0 0x9F 0x8D 0xBA 以 UTF-8 进行编码。其 UTF-16 编码改用两个 16 位代码单元(0xD83C 0xDF7A,一个 UTF-16 代理项对示例)。

使用 Win32 API 在 UTF-8 和 UTF-16 之间转换

如前段所述,根据特定 Unicode 编码,在使用不同位的计算机内存中表示 Unicode 文本。应使用何种编码? 此问题没有一个统一的答案。

最近,根据一般常识,似乎会认为较佳的方法应包括将以 UTF-8 编码的 Unicode 文本存储在跨平台 C++ 代码的 std::string 类实例中。而且,大家普遍认为,对于跨应用程序边界和跨不同计算机交换文本而言,应选择 UTF-8 编码。实际上,UTF-8 是中字节序在其中起到重要作用。任何情况下,在 UTF-8 和 UTF-16 之间转换需要至少位于 Win32 API 边界,因为支持 Windows Unicode 的 API 使用 UTF-16 作为其本机编码。

现在,我们来深入了解一些 C++ 代码以实现这些 Unicode UTF-8/UTF-16 编码转换。为实现此目标,有两个关键 Win32 API 可以使用: MultiByteToWideChar 和 its symmetric WideCharToMultiByte。可以调用前者以从 UTF-8(特定 API 术语中的“多字节”字符串)转换为 UTF-16(“宽字符”字符串);后者可用于反向转换。因为这些 Win32 函数具有相同的接口和使用方式,所以本文主要关注 MultiByteToWideChar,但在本文的下载中还包含了使用其他 API 与 C++ 兼容的代码。

使用标准 STL 字符串类存储 Unicode 文本 因为这是一篇 C++ 文章,所以通过本文可有效地使用某些字符串类存储 Unicode 文本。现在,问题来了: 可使用何种 C++ 字符串类存储 Unicode 文本? 其答案取决于用于 Unicode 文本的特定编码。如果使用 UTF-8 编码,因为它基于 8 位代码单元,所以可以使用简单的字符以 C++ 表示每个代码单元。在此情况下,一个很好的选择是使用 STL std::string 类(基于字符)来存储 UTF-8 编码的 Unicode 文本。

另一方面,如果 Unicode 文本以 UTF-16 进行编码,则按 16 位字表示每个代码单元。在 Visual C++ 中,wchar_t 类型完全是 16 位大小;因此,STL std::wstring 类(基于 wchar_t)非常适用于存储 UTF-16 Unicode 文本。

值得注意的是,C++ 标准不会指定 wchar_t 类型的大小,因此对于 Visual C++ 编译器,它相当于 16 位;而对于其他 C++ 编译器,可自由使用不同的大小。并且,实际上,Linux 上 GNU GCC C++ 编译器定义的 wchar_t 大小为 32 位。因为 wchar_t 类型在不同的编译器和平台上具有不同的大小,所以基于此类型的 std::wstring 类是不可移植的。换言之,在使用 Visual C++ 编译器的 Windows 上(wchar_t 的大小为 16 位),wstring 可用于存储以 UTF-16 编码的 Unicode 文本,但在使用 GCC C++ 编译器(定义了不同大小的 32 位 wchar_t 类型)的 Linux 上则不然。

实际上还有另一个 Unicode 编码,与同级编码相比,它的知名度较低且在实际操作中很少使用: UTF-32。正如其名,它基于 32 位代码单元。因此,对于 Linux 平台上的 UTF-32 编码,GCC/Linux 32 位 wchar_t 是一个很好的备用项。

wchar_t 大小方面的歧义导致 C++ 代码缺少可移植性(包括 std::wstring 类本身)。另一方面,基于字符的 std::string 是可移植的。但是,从实用角度来说,值得注意的是,在特定于 Windows 的 C++ 代码中,可以很好地使用 wstring 来存储 UTF-16 编码的文本。实际上,这些部分代码已与 Win32 API 交互,当然根据定义是特定于平台的。因此,将 wstring 添加到混合项中不会更改此情况。

最后,值得注意的是,因为 UTF-8 和 UTF-16 均是可变长度编码,所以通常 string::length 和 wstring::length 方法的返回值与字符串中存储的 Unicode 字符(或码位)数不对应。

转换函数接口 让我们来开发一个函数,将 UTF-8 编码的 Unicode 文本转换为使用 UTF-16 编码的对等文本。例如,你有一些跨平台 C++ 代码使用 STL std::string 类存储了 UTF-8 编码的 Unicode 字符串,且你想要将此文本传递给支持 Unicode 的 Win32 API(通常使用 UTF-16 编码),此时它可以派上用场。因为此代码涉及 Win32 API,且它不可移植,所以 std::wstring 非常适用于存储此处的 UTF-16 文本。一个可能的函数原型是:

std::wstring Utf8ToUtf16(const std::string& utf8);

此转换函数使用 Unicode UTF-8 编码的字符串(存储在标准 STL std::string 类中)形式的输入。因为这是一个输入参数,所以由 const 引用 (const &) 传递给函数。转换的结果是,返回 UTF-16 编码的字符串并存储在 std::wstring 实例中。但是 Unicode 编码转换期间,可能会出错。例如,输入 UTF-8 字符串可能包含无效的 UTF-8 序列(导致代码其他部分存在缺陷,或由于一些恶意活动导致转换终止)。在此类情况下,从安全的角度来说,最好的做法是结束转换,而非使用潜在危险的字节序列。转换函数可以通过引发 C++ 例外来处理无效的 UTF-8 输入序列情况。

定义转换错误的异常类 如果 Unicode 转换失败,可使用何种 C++ 类引发异常? 一种选择是使用标准库中已定义的类,如 std::runtime_error。但是,我倾向于针对此目的定义新的自定义 C++ 异常类,它从 std::runtime_error 派生。MultiByteToWideChar 等 Win32 API 失败时,可以调用 GetLastError 以进一步了解有关失败原因的信息。例如,如果输入字符串中存在无效的 UTF-8 序列,GetLastErorr 返回的典型错误代码是 ERROR_NO_UNICODE_­TRANSLATION。将此信息片段添加到自定义 C++ 异常类中是是有意义的;它在随后调试时会派上用场。此异常类的定义开头如下所示:

// utf8except.h
#pragma once
#include <stdint.h>   // for uint32_t
#include <stdexcept>  // for std::runtime_error
// Represents an error during UTF-8 encoding conversions
class Utf8ConversionException
  : public std::runtime_error
{
  // Error code from GetLastError()
  uint32_t _errorCode;

请注意,GetLastError 返回的值是 DWORD 类型,它表示 32 位无符号整数。但是,DWORD 是特定于 Win32 的不可移植的 typedef。即使此 C++ 异常类从 C++ 代码特定于 Win32 的部分引发,它仍会被跨平台 C++ 代码捕获! 因此,使用可移植的 typedef 而非特定于 Win32 的项是有意义的;例如,uint32_t 属于此类型。

接下来,可以定义构造函数,以初始化具有错误消息和错误代码的此自定义异常类的实例:

public:
  Utf8ConversionException(
    const char* message,
    uint32_t errorCode
  )
    : std::runtime_error(message)
    , _errorCode(errorCode)
  { }

最后,可以定义公共 getter 以提供错误代码的只读访问权限:

uint32_t ErrorCode() const
  {
    return _errorCode;
  }}; // Exception class

因为此类派生自 std::runtime_error,所以可以调用 what 方法来获取构造函数中传递的错误消息。请注意,在此类的定义中,只使用了可移植的标准元素,所以此类非常适用于 C++ 代码的跨平台部分,即使位置远离特定于 Windows 的引发点。

从 UTF-8 转换到 UTF-16: 运行中的 MultiByteToWideChar

现在,已定义转换函数原型,而自定义 C++ 异常类已实施以适当表示 UTF-8 转换失败,是时候开发转换函数主体了。正如已经预料到,使用 MultiByte­ToWideChar Win32 API 可从 UTF-8 转换到 UTF-16。术语“多字节”和“宽字符”具有历史原因。从根本上说,此 API 及其对称的 WideCharToMultiByte 最初表示在特定代码页和 Unicode 文本之间转换,这在支持 Win32 Unicode 的 API 中使用 UTF-16 编码。宽字符指 wchar_t,所以它与基于 wchar_t(UTF-16 编码的字符串)的字符串相关联。然而,多字节字符串是代码页中表示的字节序列。代码页的旧概念旨在包含 UTF-8 编码。

此 API 的典型使用方式包括首先调用 Multi­ByteToWideChar 以获取结果字符串的大小。然后,根据大小值,分配一些字符串缓冲区。如果目标是 UTF-16 字符串,通常使用 std::wstring::resize 方法来执行此操作。(有关更多详细信息,可阅读我在 2015 年 7 月发布的文章“在 Win32 API 边界使用 STL 字符串”msdn.com/magazine/mt238407。) 最后,第二次调用 MultiByteToWideChar 函数来执行实际编码转换,其中会使用先前分配的目标字符串缓冲区。请注意,同样的使用方式适用于对称的 WideCharToMultiByte API。

让我们在位于自定义 Utf8ToUtf16 转换函数的主体中的 C++ 代码中实施此方式。开始处理空输入字符串特殊情况,其中仅返回空输出 wstring:

#include <Windows.h> // For Win32 APIs
#include <string>    // For std::string and std::wstring
std::wstring Utf8ToUtf16(const std::string& utf8)
{
  std::wstring utf16; // Result
  if (utf8.empty())
  {
    return utf16;
  }

转换标记 首次调用 MultiByteToWideChar 可获取目标 UTF-16 字符串的大小。此 Win32 函数具有相对复杂的接口,且根据一些标志定义了它的行为。因为此 API 在 Utf8ToUtf16 转换函数的主体中将被调用两次,从代码可读性和可维护性方面而言,定义可同时用于两次调用的命名常数是一个很好的做法:

// Safely fails if an invalid UTF-8 character
// is encountered in the input string
constexpr DWORD kFlags = MB_ERR_INVALID_CHARS;

从安全角度而言,在输入字符串中发现无效的 UTF-8 序列时使转换进程失败也是一个很好的做法。在 Michael Howard 和 David LeBlanc 编写的“Writing Secure Code, Second Edition”(编写安全代码,第二版)(Microsoft Press,2003)一书中也建议使用 MB_ERR_INVALID_CHARS 标记。

如果你的项目使用不支持 constexpr 关键字的旧 Visual C++ 编译器,可在上下文中替代静态常数。

字符串长度和从 size_t 到 int 的安全转换 MultiByteToWideChar 期望使用 int 类型表示输入字符串长度参数,而 STL 字符串类的 length 方法返回与 size_t 相当的类型值。在 64 位版本中,Visual C++ 编译器会发出警告,指明从 size_t(大小为 8 字节)转换到 int(大小为 4 字节)时数据可能会丢失。但即使在 32 位版本中(Visual C++ 编译器将 size_t 和 int 均定义为 32 位整数),也会存在无符号/带符号不匹配:size_t 无符号而 int 带符号。对于长度合理的字符串而言,这不是问题;但对于长度大于 (231-1) 的大型字符串(即,大小高于 20 亿字节)而言,会存在问题—从无符号整数 (size_t) 转换为带符号整数 (int) 时会生成负数且负长度没有意义。

因此,与仅调用 utf8.length 来获取源 UTF-8 输入字符串的大小并将其传递给 MultiByteTo­WideChar API 不同,我们最好检查长度的实际 size_t-value,确保可以安全、有意义地转换到 int 且仅传递给 MultiByteToWideChar API。

以下代码可用于确保 size_t-length 不会超出类型 int 变量的最大值,如果超出会引发异常:

if (utf8.length() > static_cast<size_t>(std::numeric_limits<int>::max()))
{
  throw std::overflow_error(
    "Input string too long: size_t-length doesn't fit into int.");
}

请注意使用 std::numeric_limits 类模板(从 <limits> C++ 标准标头)来查询类型 int 的最大可能值。但是,此代码可能实际上未编译。这是怎么回事? 问题在于 Windows Platform SDK 标头中最小和最大宏的定义。具体而言,最大预处理器宏的特定于 Windows 的定义与 std::numeric_limits<int>::max 成员函数调用相冲突。有一些方法可以防止此问题。

一个可能的解决方案是在包括 <Windows.h> 之前 #定义 NOMINMAX。这将防止定义最小和最大特定于 Windows 的预处理器宏。但是,防止定义这些宏实际上可能会导致其他 Windows 标头(如 <gdiplus.h>)出现问题,确实需要定义这些特定于 Windows 的宏。

因此,另一个方案是围绕 std::numeric_limits::max 成员函数调用使用一对额外的圆括号,以防止上述宏扩展:

if (utf8.length() > static_cast<size_t>((std::numeric_limits<int>::max)()))
{
  throw std::overflow_error(
    "Input string too long: size_t-length doesn't fit into int.");
}

而且,作为备用项,可使用 INT_MAX 常数,而非 C++ std::numeric_limits 类模板。

无论使用何种方法,一旦完成检查大小且认为长度值适合类型 int 变量,即可使用 static_cast 安全地从 size_t 投放到 int:

// Safely convert from size_t (STL string's length)
// to int (for Win32 APIs)
const int utf8Length = static_cast<int>(utf8.length());

请注意,UTF-8 字符串的长度按 8 位字符单元测量;即,以字节为单位。

首次 API 调用: 获取目标字符串的长度 现在首次调用 MultiByteToWideChar 可获取目标 UTF-16 字符串的长度:

const int utf16Length = ::MultiByteToWideChar(
  CP_UTF8,       // Source string is in UTF-8
  kFlags,        // Conversion flags
  utf8.data(),   // Source UTF-8 string pointer
  utf8Length,    // Length of the source UTF-8 string, in chars
  nullptr,       // Unused - no conversion done in this step
  0              // Request size of destination buffer, in wchar_ts
);

注意如何调用函数以将零传递为最后一个参数。这会指示 MultiByteToWideChar API 仅返回目标字符串的所需大小;此步骤不会执行转换。还请注意,目标字符串的大小以 wchar_ts(不是以 8 位字符)表示,这很有意义,因为目标字符串是 UTF-16 编码的 Unicode 字符串,由 16 位 wchar_ts 系列生成。

要获取输入 UTF-8 std::string 的内容的只读访问权限,请调用 std::string::data 方法。因为 UTF-8 字符串的长度是以输入参数显式传递的,此代码也将适用于具有嵌入式 NUL 的 std::string 实例。

此外请注意使用 CP_UTF8 常数指定以 UTF-8 编码的输入字符串。

处理错误情况 如果预测后续函数调用会失败,例如输入字符串中存在无效的 UTF-8 序列,则 MultiByteToWideChar API 返回零。在此情况下,会调用 GetLast­Error Win32 以进一步获取有关失败原因的详细信息。存在无效的 UTF-8 字符时会返回的典型错误代码是 ERROR_NO_UNICODE_TRANSLATION。

如果失败,需要引发异常。这可以是先前自定义设计的 Utf8Conversion­Exception 类的实例:

if (utf16Length == 0)
{
  // Conversion error: capture error code and throw
  const DWORD error = ::GetLastError();
  throw Utf8ConversionException(
    "Cannot get result string length when converting " \
    "from UTF-8 to UTF-16 (MultiByteToWideChar failed).",
    error);
}

为目标字符串分配内存 如果 Win32 函数调用成功,则 utf16Length 局部变量中会存储所需的目标字符串长度,因此可以分配输出 UTF-16 字符串的目标内存。对于存储在 std::wstring 类的实例中的 UTF-16 字符串,简单调用 resize 方法即可:

utf16.resize(utf16Length);

请注意,因为输入 UTF-8 字符串的长度显式传递给了 MultiByteToWideChar(而非仅传递 -1 并要求 API 扫描整个输入字符串直至找到 NUL 结束符),Win32 API 不会向生成的字符串中添加其他 NUL 结束符: API 将仅在由显式传递的长度值指定的输入字符串中处理精确数量的字符。因此,无需使用“utf16Length + 1”值调用 std::wstring::resize: 因为 Win32 API 不会杂乱地添加其他 NUL 结束符,所以不必在目标 std::wstring 中为其留出空间(有关更多详细信息,请参阅我在 2015 年 7 月发布的文章)。

再次 API 调用: 执行实际转换 现在,UTF-16 wstring 实例具有足够的空间来托管生成的 UTF-16 编码文本,终于再次调用 MultiByteToWideChar 以获取目标字符串中实际转换的位:

// Convert from UTF-8 to UTF-16
int result = ::MultiByteToWideChar(
  CP_UTF8,       // Source string is in UTF-8
  kFlags,        // Conversion flags
  utf8.data(),   // Source UTF-8 string pointer
  utf8Length,    // Length of source UTF-8 string, in chars
  &utf16[0],     // Pointer to destination buffer
  utf16Length    // Size of destination buffer, in wchar_ts          
);

注意使用“&utf16[0]”语法获取 std::wstring 内部内存缓冲区的写入访问权限(这也已在 2015 年 7 月发布的文章中讨论过)。

如果首次调用 MultiByteToWideChar 成功,则再次调用将不会失败。但仍要检查 API 返回值是否确实是良好、安全的编码实践:

if (result == 0)
{
  // Conversion error: capture error code and throw
  const DWORD error = ::GetLastError();
  throw Utf8ConversionException(
    "Cannot convert from UTF-8 to UTF-16 "\
    "(MultiByteToWideChar failed).",
    error);
}

否则,在成功的情况下,生成的 UTF-16 字符串会最终返回到调用方:

 

return utf16;
} // End of Utf8ToUtf16

使用示例 因此,如果你有 UTF-8 编码的 Unicode 字符串(如来自一些 C++ 跨平台代码)且想要将其传递到支持 Win32 Unicode 的 API,可按如下所示调用此自定义转换函数:

std::string utf8Text = /* ...some UTF-8 Unicode text ... */;
// Convert from UTF-8 to UTF-16 at the Win32 API boundary
::SetWindowText(myWindow, Utf8ToUtf16(utf8Text).c_str());
// Note: In Unicode builds (Visual Studio default) SetWindowText
// is expanded to SetWindowTextW

Utf8ToUtf16 函数返回包含 UTF-16 编码字符串的 wstring 实例,在此实例上调用 c_str 方法以获取 NUL 结束符字符串的 C 形式原始指针,以传递到支持 Win32 Unicode 的 API。

针对从 UTF-16 反向转换到 UTF-8,可以编写非常相似的代码,此时会调用 WideCharToMultiByte API。如先前提到的,UTF-8 和 UTF-16 之间的 Unicode 转换是无损失的,转换进程期间不会丢失字符。

Unicode 编码转换库

示例可编译的 C++ 代码包含在与本文相关联的可下载存档中。此代码可重用,同时在 32 位和 64 位 Visual C++ 警告等级 4 (/W4) 进行明确编译。它被实施为纯标头 C++ 库。从基本上来说,此 Unicode 编码转换模块包含两个头文件:utf8except.h 和 utf8conv.h。前者包含 C++ 异常类的定义,用于通知 Unicode 编码转换期间的错误情况。后者会实施实际 Unicode 编码转换函数。

请注意,utf8except.h 仅包含跨平台 C++ 代码,从而可以在 C++ 项目的任何位置捕获 UTF-8 编码转换异常,包括不是特定于 Windows 的代码部分,并会按设计改用跨平台 C++。相反,utf8conv.h 包含特定于 Windows 的 C++ 代码,因为它直接与 Win32 API 边界交互。

要在项目中重用此代码,仅需 #包含上述头文件。此外,可下载的存档中还包含实施一些测试用例的其他源文件。

总结

Unicode 实际上是一个在现代软件中表示国际文本的标准。能够以多种格式对 Unicode 文本进行编码: 两个最重要的格式是 UTF-8 和 UTF-16。在 C++ Windows 代码中,通常需要在 UTF-8 和 UTF-16 之间进行转换,因为支持 Unicode 的 Win32 API 使用 UTF-16 作为其本机 Unicode 编码。UTF-8 文本可方便地存储在 STL std::string 类的实例中,而 std::wstring 非常适用于将 UTF-16 编码的文本存储在面向 Visual C++ 编译器的 Windows C++ 代码中。

可使用 Win32 API MultiByteToWideChar 和 WideCharTo­MultiByte 在以 UTF-8 和 UTF-16 编码表示的 Unicode 文本之间执行转换。我详细讲解了 MultiByteTo­WideChar API 的使用方式,将其包装在可重用的现代 C++ 帮助程序函数中以执行从 UTF-8 到 UTF-16 的转换。反向转换可按照非常类似的方式,且本文的下载中提供了可重用的 C++ 代码进行实施。


Giovanni Dicanio 是一位计算机程序员,专门研究 C++ 和 Windows,而且他还是 Pluralsight 的作者和 Visual C++ MVP。除了编程和编写课程,他还乐于在致力于 C++ 的论坛和社区中帮助其他人,可通过 giovanni.dicanio@gmail.com 与他联系。他的博客站点是 blogs.msmvps.com/gdicanio

衷心感谢以下技术专家对本文的审阅: David Cravey 和 Marc Gregoire
David Cravey 是 GlobalSCAPE 的企业架构师,负责多个 C++ 用户组,并曾四次荣膺 Visual C++ MVP。

Marc Gregoire 是一位来自比利时的高级软件工程师,他是比利时 C++ 用户组的创始人,编写了“Professional C++”(Wiley) 并合著了“C++ 标准库快速参考”(Apress),并且是许多书籍的技术编辑,在 2007 年因其 VC++ 方面的专业素养荣获年度 MVP 大奖。可以通过 marc.gregoire@nuonsoft.com 与 Marc 取得联系。