将 ANSI C 字符串转换为 UNICODE

Converting an ANSI C-String to UNICODE

注意:我正在尝试编写自己的函数来执行此转换

我知道 char 是 1 个字节,而 wchar_t 是 2 个字节。

这就是转换的发生方式:

1) 输入文字

Hello, world

2) 获取字符串的字节数

48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21

3) 分配两倍字节数的内存

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

4) 用ANSI值填充一个字节,一次跳过一个字节

48 00 65 00 6c 00 6c 00 6f 00 2c 00 20 00 77 00 6f 00 72 00 6c 00 64 00 21 00

关于这个过程我有几个问题:

1) 我可以简单地将一个 ANSI 字符串转换为 UNICODE 并让它复制上面的确切过程,还是简单地用 ANSI 字节填充前半部分字节并将其余部分保留为 0?

char a[] = { "Hello, world!" };
wchar_t* b = reinterpret_cast<wchar_t*>(a);

2) 查看 MultiByteToWideChar 函数,我看到一个 CodePage 参数,我想知道它是什么。转换不都是一样的吗(按照我的理解并在上面写出来)?我认为 ASCII 字符代码在任何地方都是一样的,但是如果我从它具有 Mac 和 Windows 的值的事实中正确理解的话,这个论点似乎不是这样说的。

I thought the ASCII character codes were all the same everywhere, but this argument seems to say otherwise if I am understanding correctly from the fact it has values for Mac and Windows there.

ASCII 代码是,是的,但是 "Extended ASCII" 字符串的高位(剧透:没有这样的东西)映射到大量 代码页中的任何一个 ,所有不同的编码主要用于不同的地理区域。您采用的方法适用于简单、普通的 ASCII 情况,但通常不起作用,MultiByteToWideChar 知道这一点。它将正确地从您使用的任何代码页重新编码为 Windows 令人困惑地称为 "Unicode"(而不是 "UNICODE")的内容,实际上更具体地说是 "UTF-16" 编码。

Can I simply cast an ANSI string to UNICODE and have it replicate the exact process above, or will it simply fill the first half of the bytes with the ANSI bytes and leave the rest to 0?

没有。演员不会重新编码事物或更改值。在那里你只是说“我保证 a 是一堆 wchar_ts,即使它有类型 char* (它没有,它有数组类型,但足够接近今天)。

如果您使用 b,该代码实际上有未定义的行为,因为您违反了别名规则(您可以通过 char* 检查 T,但您不能不要将 char[] 视为您从未创建的 T)。但是,如果没有,您会发现您的 "string" 现在是长度的一半,并且很可能是无效的 UTF-16 序列,无法在任何地方正确呈现。

So if I wanted to support UTF-32, I would have to create my own wrapper for strings since wchar_t is only 2 bytes long and I need 4 bytes, and also I would not be able to print it with printf for example, correct?

从技术上讲,是的(尽管您会使用像 libicu 这样的库,而不是自己动手)。

但是,实际上,您并不想使用 UTF-32。使用 Windows API 你会被 UTF-16 困住,但除此之外,我们通常更喜欢 UTF-8 而不是 char,它很好,便携,灵活,很好,很好. (虽然你会再次需要一个图书馆。)

然后由您决定在何处执行相关转换,and/or您是否有根据平台从 UTF-8 切换到 UTF-16 的开关(例如 Windows 的旧 UNICODE 宏)或到处都是 运行 UTF-8,直到你到达 Windows API 边界。

或者,如果您的所有输入都像您暗示的那样是 ASCII,那么您实际上不需要做任何事情,除了您已经做的:要么在整个程序中保留 ASCII,但在使用时将其转换为 UTF-16 Windows API,或者在整个程序中使用 UTF-16(和 wchar_ts,并且没有转换。确保使用你最喜欢的函数的宽字符版本,但是(比如wprintf) 如果你沿着那条路走下去。

您尝试执行的操作仅适用于 0..127 范围内的 ASCII 字符代码。这些字符在 Unicode 中具有相同的数值,因此可以在 charwchar_t 字符串之间按原样复制。

而且不,你不能只reinterpret_cast char数据的内存地址到wchar_t*,你需要分配一个新的wchar_t数组和复制值,例如:

char a[] = { "Hello, world!" };
wchar_t* b = new wchar_t[sizeof(a) * sizeof(wchar_t)];
for(size_t i = 0; i < sizeof(a); ++i) {
    b[i] = static_cast<wchar_t>(a[i]);
}
...
delete[] b;

这种类型的复制最好使用 std::stringstd::wstring 基于迭代器的构造函数来处理,例如:

std::string a = "Hello, world!";
std::wstring b(a.begin(), a.end());
...

但是,超出 ASCII 范围,您需要通过 charset/codepage 查找 转换 charwchar_t 之间的数据。不同的 charsets/codepages 以不同的方式编码 Unicode 字符。 MultiByteToWideChar()(和 WideCharToMultiByte())使用您告诉它使用的代码页为您处理这些转换。还有许多第三方库也可以处理这些转换,例如 ICONV、ICU 等。在某种程度上,甚至 C++ 自己的 std::wstring_convertstd::wbuffer_convert 也可以(尽管它们在 C 中已弃用) ++17 起)。

例如,让我们看一下代码点U+20AC EURO SIGN ():

  • 在一个wchar_t字符串中,占用一个wchar_t,其数值为0x20AC.

  • 在UTF-8编码的char字符串中,占用3chars,其数值为0xE2 0x82 0xAC.

  • 在一个Windows-1252编码的char字符串中,它占用了一个char,其数值为0x80.

  • 在 Latin-1 (ISO-8859-1) 编码的 char 字符串中,欧元符号甚至没有分配数值!

因此,对于非 ASCII 字符,简单的值复制是不够的。