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字符串的转换,反之亦然-
我需要对迁移此代码进行哪些更改,以便它在Visual Studio Unicode和Unicode启用的库的生态系统中工作? (我不需要它同时使用ASCII和Unicode,它可以是纯Unicode。)
-
是否也可以以独立于平台的方式执行此操作? (即,不使用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.h
function,现在可以从代码中删除所有宏:
-
_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_t
和char
用户提供一个库)。
但如果不是这种情况,如果Windows不是您的主要平台,那么有一个假设所有char和std :: string将包含UTF-8字符的解决方案,除非被告知不同。 然后,您需要包装API以确保您的char UTF-8字符串不会被误认为是Windows上的ANSI(或其他代码分页)字符串。 例如,将假定stdio.h
和iostream
库的文件名称是代码分段的,以及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::string
和std::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替换字符串
那一切都应该有效。