Visual C++:将传统 C 和 C++ 字符串代码迁移到 Unicode 世界

Posted

技术标签:

【中文标题】Visual C++:将传统 C 和 C++ 字符串代码迁移到 Unicode 世界【英文标题】:Visual C++: Migrating traditional C and C++ string code to a Unicode world 【发布时间】:2011-01-04 13:53:58 【问题描述】:

我看到 Visual Studio 2008 及更高版本现在开始使用字符集设置为 Unicode 的新解决方案。我的旧 C++ 代码只处理英文 ASCII 文本,并且充满了:

文字字符串,如"Hello World" char类型 char * 指向已分配 C 字符串的指针 STL string类型

使用STL string 构造函数(接受const char *)和STL string.c_str()STL string 转换为C 字符串,反之亦然

    迁移此代码以使其在 Visual Studio Unicode 和支持 Unicode 的库的生态系统中工作需要进行哪些更改? (我真的不需要它同时使用 ASCII 和 Unicode,它可以是纯 Unicode。)

    是否也可以以独立于平台的方式执行此操作? (即,不使用 Microsoft 类型。)

我看到很多宽字符和 Unicode 类型和转换分散在各处,因此我很困惑。 (例如:wchar_t、TCHAR、_T、_TEXT、TEXT 等)

【问题讨论】:

看看这篇文章 - joelonsoftware.com/articles/Unicode.html - 那里有一些关于 Unicode 的良好背景知识。 【参考方案1】:

我非常反对L""_T()std::wstring(后者不是多平台的)和微软关于如何做Unicode的建议。

在这个问题上有很多困惑。有些人仍然认为 Unicode == 2 字节字符 == UTF-16。两个等式都不正确。

事实上,可能,甚至更好地保留 char* 和普通的 std::string,普通的文字并且改变很少(并且仍然完全支持 Unicode!)。

请在此处查看我的答案:https://***.com/questions/1049947/should-utf-16-be-considered-harmful/1855375#1855375,了解如何以最简单的方式(在我看来)。

【讨论】:

许多 Microsoft 的文档使用术语“Unicode”作为“UTF-16”或“UCS-2”的同义词 这是有原因的。当 MS 刚开始国际化时,人们认为“widechar”是可能的。 @AAT:我不同意微软对 UTF-16 的支持。例如,当您尝试在记事本中删除一个 4 字节的 UTF-16 字符时,文本将变为无效。我建议仅在 MFC/API 调用附近转换为 UTF-16。至少,我只为 Windows 编程,经过麻烦之后我更喜欢 UTF-8。 Pavel:仅在 Windows 领域。在 UNIX 中,wchar_t 通常为 4 个字节,并存储 UCS-4 编码字符。 @caf:这可能是 UNIX 人更喜欢 UTF-8 的原因 :)【参考方案2】:

注意:哇...显然,有人认为几乎所有答案都应该降级,即使是正确的...我自己承担了升级它们以平衡降级...

让我们看看我是否有自己的downmod...:-/

编辑:高兴!!!

九小时前,某人(可能是对每个答案都投反对票的人,但 Pavel Radzivilovsky 的那个人)对这个答案投了反对票。当然,没有任何评论指出我的答案有什么问题。

\o/

1 - 如何在 Windows Unicode 上迁移?

我需要进行哪些更改才能迁移此代码以使其在 Visual Studio Unicode 和支持 Unicode 的库的生态系统中工作? (我真的不需要它同时使用 ASCII 和 Unicode,它可以是纯 Unicode。)

1.a - 我的代码库很大,我不能一步完成!

让我们想象一下,您想逐步进行(因为您的应用并不小)。

我在我的团队中遇到了同样的问题:我想生成支持 Unicode 的代码与不支持 Unicode 的代码共存。

为此,您必须使用 MS 的标头 tchar.h,并使用其设施。使用您自己的示例:

"Hello World" ----> _T("Hello World") char 类型 ----> TCHAR 类型 char * 指向已分配 C 字符串的指针 ----> TCHAR * 指针 std::string 类型 ---> 这很棘手,因为您必须创建自己的 std::tstring 请记住 sizeof(char) 可能与 sizeof(TCHAR) 不同,因此也要更新您的 malloc 和 new[]

1.b - 你自己的tstring.hpp 标头

要使用我的编译器处理 STL(当时,我正在使用 Visual C++ 2003,所以你的里程可能会有所不同),我必须提供一个 tstring.hpp 标头,它既是跨平台的,又使用户能够使用 tstring、tiostream 等。我不能把完整的源代码放在这里,但我会给出一个摘录,让你可​​以自己制作:

namespace std


#ifdef _MSC_VER

#ifdef UNICODE
typedef             wstring                         tstring ;
typedef             wistream                        tistream ;
// etc.
#else // Not UNICODE
typedef             string                          tstring ;
typedef             istream                         tistream ;
// etc.
#endif

#endif

 // namespace std

通常情况下,它是无权污染std命名空间的,但我想这没问题(并且已经测试过了)。

这样,您可以为大多数 STL/C++ iostream 构造添加前缀 t,并使其准备好 Unicode(在 Windows 上)。

1.c - 完成了!!!

现在,您可以通过定义 UNICODE_UNICODE 定义从 ANSI 模式切换到 UNICODE 模式,通常在项目设置中(我记得在 Visual C++ 2008 上,第一个设置页面中的条目正是为此)。

我的建议是,由于您的 Visual C++ 项目可能有“调试”和“发布”模式,因此创建从它们派生的“调试 Unicode”和“发布 Unicode”模式,其中上述宏是已定义。

因此,您将能够生成 ANSI 和 UNICODE 二进制文件。

1.d - 现在,一切都是(或应该是)Unicode!

如果您希望您的应用是跨平台的,请忽略此部分。

现在,您可以一步修改所有代码库,或者您已经将所有代码库转换为使用上述tchar.h 功能,您现在可以从代码中删除所有宏:

_T("Hello World") ----> L"Hello World" TCHAR 类型 ----> wchar_t 类型 TCHAR * 指向已分配 C 字符串的指针 ----> wchar_t * 指针 std::tstring 类型 ---> std::wstring 类型等

1.e - 记住 UTF-16 字形在 Windows 上可以是 1 或 2 wchar_t 宽!

Windows 上的一个常见误解是认为 wchar_t 字符是一个 Unicode 字形。这是错误的,因为某些 Unicode 字形由两个 wchar_t 表示。

因此,如果您使用不是来自 BMP 的 Unicode 字形,任何依赖于一个 char 作为一个字形的代码都可能会中断。

2 - 跨平台吗?

是否也可以以独立于平台的方式执行此操作? (即,不使用 Microsoft 类型。)

现在,这是棘手的部分。

Linux(我不知道其他操作系统,但应该很容易从 Linux 或 Windows 解决方案推断出来)现在可以使用 Unicode,char 类型应该包含 UTF-8 值。

这意味着您的应用程序一旦编译,例如在我的 Ubuntu 10.04 上,默认为 Unicode。

2.a - 记住 UTF-8 字形在 Linux 上可以是 1、2、3 或 4 个字符宽!

当然,上面关于 UTF-16 和宽字符的建议在这里更为重要:

一个 Unicode 字形可能需要 1 到 4 个 char 字符来表示。因此,您使用的任何代码都依赖于每个 char 是独立 Unicode 字符的假设,都会中断。

2.b - Linux 上没有tchar.h

我的解决方案:写下来。

您只需要定义以“t”为前缀的符号即可映射到普通符号上,如本节选所示:

#ifdef __GNUC__

#ifdef  __cplusplus
extern "C" 
#endif

#define _TEOF       EOF

#define __T(x)      x

// etc.
#define _tmain      main

// etc.

#define _tprintf    printf
#define _ftprintf   fprintf

// etc.

#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

#ifdef  __cplusplus

#endif

#endif // __GNUC__

...并将其包含在 Linux 上,而不是包含 Windows 中的 tchar.h

2.c - Linux 上没有tstring

当然,上面为 Windows 做的 STL 映射应该已经完成​​,以处理 Linux 的情况:

namespace std


#ifdef _MSC_VER

#ifdef UNICODE
typedef             wstring                         tstring ;
typedef             wistream                        tistream ;
// etc.
#else // Not UNICODE
typedef             string                          tstring ;
typedef             istream                         tistream ;
// etc.
#endif

#elif defined(__GNUC__)
typedef             string                          tstring ;
typedef             istream                         tistream ;
// etc.
#endif

 // namespace std

现在,您可以在 Linux 和 Windows 上使用 _T("Hello World")std::tstring

3 - 一定有一个陷阱!

还有。

首先,存在std命名空间被你自己的t前缀符号污染的问题,这应该是被禁止的。然后,不要忘记添加宏,这会污染你的代码。在目前的情况下,我想这没问题。

二,我猜你在 Windows 上使用 MSVC(因此是宏 _MSC_VER)和在 Linux 上使用 GCC(因此是宏 __GNUC__)。如果您的情况不同,请修改定义。

三,你的代码必须是 Unicode 中性的,也就是说,你不能依赖你的字符串是 UTF-8 或 UTF-16。事实上,为了保持跨平台兼容,您的源代码应该是空的,除了 ASCII 字符。

这意味着某些功能(例如搜索 ONE Unicode Glyph 的存在)必须通过单独的一段代码来完成,该代码将包含所有#define 以使其正确。

例如,在 Windows 上使用 UTF-16 wchar_t 时,搜索字符 é(Unicode 字形 233)需要搜索第一个字符 233,在 UTF-8 上搜索两个字符的第一个序列 195 和 169 char。这意味着您必须使用一些 Unicode 库来完成,或者自己编写。

但这更多是 Unicode 本身的问题,而不是 Windows 或 Linux 上的 Unicode。

3.a - 但是 Windows 应该不能正确处理 UTF-16

那又怎样?

我看到描述的“规范”示例是 EDIT Win32 控件,它应该无法在 Windows 上正确退格非 BMP UTF-16 字符(不是我没有验证错误,我只是没有足够关心)。

这是微软的问题。您在代码中所做的任何决定都不会改变 Win32 API 中存在或不存在此错误的事实。因此,在 Windows 上使用 UTF-8 字符不会纠正 EDIT 控件上的错误。您唯一希望做的就是创建自己的 EDIT 控件(子类化并正确处理 BACKSPACE 事件?)或您自己的转换函数。

不要混合两个不同的问题,即:Windows API 中的一个假定错误您自己的代码。除非您不使用假定的错误 Windows API,否则您自己的代码中的任何内容都无法避免 Windows API 中的错误。

3.b - 但是 Windows 上的 UTF-16,Linux 上的 UTF-8,不是很复杂吗?

是的,如果您对角色假设过多,它可能会导致某些平台上出现在其他平台上不会发生的错误。

我假设您的主要平台是 Windows(或者您想为 wchar_tchar 用户提供一个库)。

但如果不是这种情况,如果 Windows 不是您的主要平台,那么可以假设您的所有 char 和 std::string 都将包含 UTF-8 字符,除非另有说明。然后,您需要包装 API 以确保您的 char UTF-8 字符串不会被误认为是 Windows 上的 ANSI(或其他代码页)char 字符串。例如,stdio.hiostream 库的文件名以及 Win32 API 的 ANSI 版本(例如 CreateWindowA)将被假定为代码页。

这是使用 UTF-8 字符的 GTK+ 的方法,但令人惊讶的是,不是使用 UTF-16 的 QT(构建 Linux KDE)的方法。

来源:

QT:http://doc.qt.nokia.com/4.6/qstring.html#details GTK+ : http://www.gtk.org/api/2.6/glib/glib-Character-Set-Conversion.html#filename-utf-8

不过,它不会保护您免受“嘿,但是 Win32 编辑控件不处理我的 Unicode 字符!”的影响。问题,因此您仍然必须对该控件进行子类化以获得所需的行为(如果错误仍然存​​在)...

附录

在std::wstring VS std::string 上查看我的回答,了解std::stringstd::wstring 之间的完全区别。

【讨论】:

很棒的帖子,只是关于 2c 的注释。对于 Linux,可能可以跳过 __GNUC__ 检查,因为 UNICODE 检查将为 Linux 正确定义字符串类型,假设没有为 gcc/clang 编译定义 UNICODE。【参考方案3】:

“你好世界”-> L“你好世界”

char -> wchar_t(除非你真的想要 char)

char * -> wchar_t *

字符串 -> wstring

这些都是独立于平台的。但是请注意,宽字符在不同平台上可能不同(Windows 上两个字节,其他四个字节)。

在您的项目中定义 UNICODE 和 _UNICODE(在 Visual Studio 中,您可以通过在设置中将项目设置为使用 Unicode 来做到这一点)。这也使 _T、TCHAR、_TEXT 和 TEXT 宏自动变为 L。这些是 Microsoft 特定的,因此如果您想跨平台,请避免使用这些。

【讨论】:

【参考方案4】:

我建议不要担心同时支持 ascii 和 unicode 构建(a-la TCHAR),直接使用 unicode。这样,您就可以使用更多与平台无关的函数(wcscpy、wcsstr 等),而不是依赖于 Micrpsoft 特定的 TCHAR 函数。

您可以使用 std::wstring 代替 std::string 并将所有 chars 替换为 wchar_ts。通过像这样的巨大改变,我发现你从一件事开始,让编译器引导你到下一件事。

我能想到的在运行时可能并不明显的一件事是使用 malloc 分配字符串而不使用 sizeof 运算符作为基础类型。所以要注意char * p = (char*)malloc(11) - 10 个字符加上终止 NULL 之类的东西,这个字符串将是它应该在wchar_ts 中的一半大小。它应该变成wchar_t * p = (wchar_t*)malloc(11*sizeof(wchar_t))

哦,整个TCHAR 是支持编译时ASCII/Unicode 字符串。它是这样定义的:

#ifdef _UNICODE
#define _T(x) L ## x
#else
#define _T(x) ## x
#endif

因此,在 unicode 配置中 _T("blah") 变为 L"blah" 而在 ascii 配置中为 "blah"

【讨论】:

感谢您的有用回答。我没有真正需要同时支持 ASCII 和 Unicode。所以,它是全速进入Unicode然后:-) -1:“这个字符串将是它应该在 UNICODE 中的一半大小”是错误的。使用 wchar_t,字符最多可以有 4 个字节,具体取决于实际内容。 这是 UTF16 编码中的一个边缘情况,不适用于曾经是 ASCII 的文本。我要说的是转换假定 1 字节 = 1 个字符的代码。为了让该代码在 UCS2 下工作,假设 2 个字节 = 1 个字符是 100% 正确的。 已将 UNICODE 更改为 wchar_t【参考方案5】:

您的问题涉及两个不同但相关的概念。其中之一是字符串的编码(例如 Unicode/ASCII)。另一个是用于字符表示的数据类型。

从技术上讲,您可以使用普通的 char 和 std::string 来创建 Unicode 应用程序。您可以使用十六进制 ("\x5FA") 或八进制 ("\05FA") 格式的文字来指定字符串的字节序列。请注意,使用这种方法,您已经存在的包含 ASCII 字符的字符串文字应该保持有效,因为 Unicode 保留了 ASCII 中的代码。

需要注意的重要一点是,许多与字符串相关的函数都需要谨慎使用。这是因为它们将在 bytes 而不是 characters 上运行。例如,std::string::operator[] 可能会给您一个特定的字节,它只是 Unicode 字符的一部分。

在 Visual Studio 中,wchar_t 被选为基础字符类型。因此,如果您正在使用基于 Microsoft 的库,如果您遵循其他人在此处发布的许多建议,事情应该会变得更容易。将 char 替换为 wchar_t,使用“T”宏(如果您想保持 Unicode/非 Unicode 之间的透明度)等。

但是,我认为跨库使用 Unicode 没有事实上的标准,因为它们可能有不同的策略来处理它。

【讨论】:

主要问题是 Microsoft API 也不正确支持 wchar_t。众所周知,在 Windows 文本框中,如果某些字符的编码中有两个以上的 wchar,则不能使用一个退格键删除某些字符。另外:Unicode 和 ASCII 不是编码。 好吧,也许您对 encoding 这个词使用了不同的含义,但 ascii、utf-8、utf-16 等实际上是字符编码。关于你的其他 cmets...我不明白我制作的任何 cmets 怎么可能与他们发生冲突。它们只是附加信息。【参考方案6】: 使用 _T() 围绕您的文字常量,例如_T("你好世界") 将char 替换为宏CHARstring 替换为 wstring

那么一切都应该工作了。

【讨论】:

即使迁移到 Unicode 就像搜索和替换一样简单,对字符串使用 wstring,对字符使用 CHAR,而字符串文字可能是charwchar_t工作。你必须保持一致。

以上是关于Visual C++:将传统 C 和 C++ 字符串代码迁移到 Unicode 世界的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Visual Studio C++ 中将西里尔文(俄语)字符输出到控制台?

Visual C++ 2017,外部“C”被忽略了吗?将 C++ 代码链接到 C 库时出现 LNK2019 错误

如何在 VIsual C++ 2010 中实现 C++ 原始字符串文字?

Visual Studio 2013 C++ IntelliSense 显示建议,但不会通过按任何成员列表提交字符来提交它们

visual c++是啥?

如何将 VSCode 与 Visual C++ 编译器集成?