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

u1ehiz5o  于 2023-10-15  发布在  其他
关注(0)|答案(6)|浏览(150)

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

  • "Hello World"这样的文字字符串
  • char
  • char *指向已分配的C字符串的指针
  • STL string

1.使用STL string构造函数(接受const char *)和STL string.c_str()STL string转换为C字符串,反之亦然
1.我需要做哪些更改才能迁移此代码,使其在Visual Studio Unicode和启用Unicode的库的生态系统中工作?(我没有真实的需要它同时使用ASCII和Unicode,它可以是纯Unicode。)
1.是否也可以以独立于平台的方式实现这一点?(即不使用Microsoft类型。)
我看到这么多宽字符和Unicode类型和转换分散在周围,因此我的困惑。(例如:wchar_t、TCHAR、_T、_TEXT、TEXT等)

8i9zcol2

8i9zcol21#

Note: Wow... Apparently, SOMEONE decided that ALMOST all answers deserved a downmod, even when correct... I took upon myself of upmoding them to balance the downmod...
``
Let's see if I have my own downmod... :-/

编辑:REJOICE!!!

九个小时前,someone(可能是那个否决了除了帕维尔·拉齐维洛夫斯基之外的所有答案的人)否决了这个答案。当然,没有任何评论指出我的答案有什么问题。
\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 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名称空间,但我猜这是好的(它被测试为好的)。
这样,您可以在大多数STL/C++ iostreams构造中添加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 -记住,在Linux上,UTF-8字形可以是1、2、3或4个字符宽!

当然,上面关于UTF-16和宽字符的建议在这里更重要:
一个Unicode编码可能需要1到4个char字符来表示。因此,任何依赖于假设每个char都是独立Unicode字符的代码都将中断。

2.b -Linux上没有tchar.h

我的解决方案:写的
您只需要定义前缀为“t”的符号来Map普通符号,如以下摘录所示:

#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完成的STLMap应该完成以处理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字符之外什么都没有。
这意味着一些功能,比如搜索一个Unicode Glance的存在,必须由一段单独的代码来完成,这段代码将包含所有正确的#define
例如,在Windows上使用UTF-16 wchar_t时,搜索字符é(Unicode Glance 233)需要搜索第一个字符233,而在UTF-8 char上搜索第一个由两个字符195和169组成的序列。这意味着你必须使用一些Unicode库来完成它,或者自己编写它。
但这更多的是Unicode本身的问题,而不是Windows或Linux上的Unicode。

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

那又怎样?
我看到的“规范”例子是EDIT Win32控件,它应该无法在Windows上正确地退格非BMP UTF-16字符(并不是说我没有验证这个错误,我只是不够关心)。
这是微软的问题。您在代码中决定的任何内容都不会改变Win32 API中是否存在此错误的事实。因此在Windows上使用UTF-8字符不会纠正EDIT控件上的错误。您唯一希望做的事情就是创建自己的EDIT控件(将其子类化并正确处理EDITSPACE事件?)或您自己的转换函数。
不要混淆两个不同的问题,即:a supposed bug in the Windows APIyour own code。在你自己的代码中没有任何东西可以避免Windows API中的错误,除非你不使用假定的有错误的Windows API。

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

是的,如果你对角色假设太多,它可能会导致一些平台上的bug,而这些bug在其他平台上不会发生。
我假设您的主要平台是Windows(或者您希望为wchar_tchar用户提供一个库)。
但如果不是这样,如果Windows不是你的主要平台,那么有一个解决方案,假设所有的char和std::string都包含UTF-8字符,除非另有说明。然后,您需要 Package API,以确保您的字符UTF-8字符串不会被误认为是Windows上的ANSI(或其他代码封装)字符串。例如,stdio.hiostream库的文件名将被假定为已编码,以及Win32 API的ANSI版本(例如,Windows WindowA)。
这是使用UTF-8字符的GTK+的方法,但并不奇怪,QT(Linux KDE构建于此)使用UTF-16。
资料来源:

  • 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::stringstd::wstring之间的完全区别,请参阅我在std::wstring VS std::string上的回答。

yyhrrdl8

yyhrrdl82#

我非常推荐反对L""_T()std::wstring(后者不是多平台的)和微软关于如何做Unicode的建议。
在这个问题上有很多困惑。有些人仍然认为Unicode == 2字节字符== UTF-16。这两种平等都是不正确的。
事实上,这是可能的,甚至更好地保持char* 和普通的std::string,普通的文字和变化很少(仍然完全支持Unicode!).
请看我在“UTF-16应该被认为是有害的吗?”“:https://softwareengineering.stackexchange.com/a/102215如何做它最简单的(在我看来)方法。

g9icjywg

g9icjywg3#

我建议不要担心同时支持asphalt和unicode build(a-la TCHAR),直接使用unicode。这样你就可以使用更多的平台无关的函数(wcscpy,wcsstr等),而不是依赖于TCHAR函数,这些函数是Micrpsoft特定的。
您可以使用std::wstring而不是std::string,并将所有char替换为wchar_t。有了这样一个巨大的变化,我发现你从一件事开始,让编译器引导你到下一件事。
我能想到的在运行时可能不明显的一件事是,在没有对底层类型使用sizeof操作符的情况下,用malloc分配字符串。所以要注意像char * p = (char*)malloc(11)- 10个字符加上终止NULL这样的东西,这个字符串将是wchar_t s中应有大小的一半。应该是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",而在asc配置中,它是"blah"

w8ntj3qf

w8ntj3qf4#

“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。这些都是微软特有的,所以如果你想跨平台的话就避免这些。

cig3rfwq

cig3rfwq5#

你的问题涉及两个不同但相关的概念。其中之一是字符串的编码(例如Unicode/ASCII)。另一个是用于字符表示的数据类型。
从技术上讲,你可以有一个使用普通char和std::string的Unicode应用程序。您可以使用十六进制(“\x5FA”)或八进制(“\05FA”)格式的文字来指定字符串的字节序列。注意,使用这种方法,已经存在的包含ASCII字符的字符串文字应该仍然有效,因为Unicode保留了ASCII的代码。
需要注意的一点是,许多与字符串相关的函数需要小心使用。这是因为它们将对字节而不是字符进行操作。例如,std::string::operator[]可能会给予一个特定的字节,它只是Unicode字符的一部分。
在Visual Studio中,wchar_t被选为基础字符类型。因此,如果你正在使用基于微软的库,如果你遵循了其他人在这里发布的许多建议,事情应该会变得更容易。用wchar_t替换char,使用“T”宏(如果你想保持Unicode/非Unicode之间的透明性),等等。
然而,我不认为跨库使用Unicode有一个事实上的标准,因为它们可能有不同的策略来处理它。

oaxa6hgo

oaxa6hgo6#

  • 用_T()包围你的常量,例如。_T(“Hello world”)
  • char替换为宏CHAR
  • string替换为wstring

所有的工作都应该工作。

相关问题