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

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

  • "Hello World"这样的文字字符串
  • char类型
  • char *指向已分配C字符串的指针
  • STL string类型
  • 使用STL string构造函数(接受const char * )和STL string.c_str()STL string到C字符串的转换,反之亦然

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

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

我看到很多广泛的字符和Unicode类型和转换散落在周围,因此我的困惑。 (例如:wchar_t,TCHAR,_T,_TEXT,TEXT等)

注意:哇…显然,有人决定ALMOST所有的答案都应该得到一个downmod,即使是正确的…我自己对它进行了修改以平衡downmod …

让我们看看我是否有自己的downmod ……: – /

编辑:REJOICE !!!

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

\ O /

1 – 如何在Windows Unicode上迁移?

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

1.a – 我的代码库很大,我不能一步到位!

让我们想象你想逐渐做到这一点(因为你的应用程序不小)。

我的团队遇到了同样的问题:我想生成Unicode准备好的代码,这些代码与非Unicode准备好的代码共存。

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

  • "Hello World" —-> _T("Hello World")
  • char类型—-> TCHAR类型
  • char *指向分配的C字符串—-> TCHAR *指针
  • std::string type —>这很棘手,因为你必须创建自己的std::tstring
  • 请记住,sizeof(char)可以与sizeof(TCHAR)不同,所以也要更新mallocs和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命名空间,但我想这是好的(并且测试好了)。

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

1.c – 已经完成!!!

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

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

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

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

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

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

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

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

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

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

2 – 跨平台做吗?

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

现在,这是棘手的部分。

Linux(我不知道其他操作系统,但它很容易从Linux或Windows解决方案推断)现在是Unicode准备好的, char类型应该包含UTF-8值。

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

2.a – 请记住,Linux上的UTF-8字形可以是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命名空间的问题,这应该被禁止。 然后,不要忘记宏上的添加,这将污染您的代码。 在目前的情况下,我猜这是好的。

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

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

这意味着某些function,例如搜索ONE Unicode Glyph的存在,必须通过单独的代码完成,这将使所有#define成为正确的。

例如,搜索字符é (Unicode Glyph 233)将需要您在Windows上使用UTF-16 wchar_t时搜索第一个字符233,以及UTF-8 char上的两个字符195和169的第一个序列。 这意味着您必须使用某些Unicode库来执行此操作,或者自己编写它。

但在Windows或Linux上,这更像是Unicode本身的问题。

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

所以呢?

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

这是Microsoft的问题。 您在代码中决定的任何内容都不会改变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(或其他代码分页)字符串。 例如,将假定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字符!” 问题,所以你仍然必须将该控件子类化以获得所需的行为(如果bug仍然存在)……

附录

请参阅我在std :: wstring VS std :: string的回答,了解std::stringstd::wstring之间的完全区别。

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

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

事实上,使用char *和普通的std::string ,普通文字并且变化很小(并且仍然完全支持Unicode!)是可能的 ,甚至更好。

请参阅我的回答: https : //stackoverflow.com/questions/1049947/should-utf-16-be-considered-harmful/1855375#1855375 ,了解如何以最简单的方式(在我看来)这样做。

“Hello World” – > L“Hello World”

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

char * – > wchar_t *

string – > wstring

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

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

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

您可以使用std :: wstring而不是std :: string,并用wchar_t替换所有char 。 通过这样的巨大变化,我发现你从一件事开始,让编译器引导你到下一个。

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

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

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

需要注意的一点是,需要仔细使用许多与字符串相关的函数。 这是因为它们将在字节而不是字符上运行 。 例如, std::string::operator[]可能会为您提供仅作为Unicode字符一部分的特定字节。

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

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

  • 用_T​​()围绕你的文字常量,例如_T(“Hello world”)
  • 用宏CHAR替换char
  • wstring替换字符串

那一切都应该有效。