wchar 格式控制符_[C] 跨平台使用TCHAR——让Linux等平台也支持tchar.h,解决跨平台时的格式控制字符问题,多国语言的同时显示(兼容vc/gcc/bcb,支持Windows/Lin...

将Windows程序移植到Linux等平台时,经常会遇到tchar.h问题与字符串的格式控制字符问题(char串、wchar_t串、TCHAR串混合输出)。本文探讨如何解决这些问题。

一、背景

1.1 历史

传统的C程序使用char字符串,采用ANSI+DBCS方案来支持当地语言,不能实现多国语言同时显示。

当年微软在设计Windows NT时考虑到国际化,决定内核支持Unicode,对应wchar_t类型。那时的Unicode只有16位,于是Windows中的wchar_t是16位的。

为了兼容老程序,与字符串有关的API一般有两套——A结尾的表示是ANSI版,使用char字符串;W结尾的是Unicode版,使用wchar_t字符串。

两套API用起来不方便,于是微软设计了tchar.h,定义了TCHAR类型,使用宏来切换。只需编写一份代码,就可分别编译为ANSI版与Unicode版,分别兼容老系统(win9X)和新系统(winNT)。

Linux等平台较晚才支持Unicode,那时已经有成熟的UTF-8编码方案,兼容传统的char类型。于是Linux等平台将UTF-8作为默认编码,这样不仅支持Unicode多国语言,而且传统的C标准库、POSIX等API均能正常工作。两全其美,不再需要搞两套API,自然也不需要tchar.h。

UTF-8是变长编码,一个字符可能是1至4字节,处理起来不太方便。于是Linux等平台也提供了wchar_t类型,只不过它是32位的。

为什么是32位的的呢,这与Unicode的发展有关。由于Unicode需要收录的东西太多,16位早就不够用了。

UCS-4 提倡31位的编码空间,并提出了UTF-32和6字节UTF-8等编码方案。可是该方案的成本很高。

进过折衷考虑,Unicode组织将编码空间由16位的0至FFFF,升级至21位的0至10FFFF。将传统16位Unicode编码称为UTF-16,并提供代理对(surrogate)方案,用两个UTF-16字符单元来编码超过16位的字符。

也就是说,如果wchar_t类型是16位的话,那它实际上代表UTF-16编码——对于在U+0000至U+FFFF之间的字符,每个字符占1个wchar_t;对于在U+10000至U+10FFFF之间的字符,每个字符占2个wchar_t。

为了确保每个字符都只占1个wchar_t,那就得将wchar_t定义为32位。这也就是UTF-32编码。

虽然UTF-8编码方案本身能表达很大的编码空间(例如6字节UTF-8可编码31位),但为了规范化,RFC 3629规定UTF-8最长为4字节,即最高21位编码,超过10FFFF的编码点是无效的。

1.2 为什么需要让Linux等平台也支持tchar.h?

很多人认为Linux等平台没必要支持tchar.h,这主要是因为wchar_t的一些问题——

1. UTF-8编码的char类型能满足Unicode国际化需求。

2. char类型更容易跨平台。而wchar_t是C95修订中加入的,到C99标准才有比较完善的支持,故某些旧编译器对wchar_t支持性不佳、甚至完全不支持。

3. wchar_t的位数不固定。在Windows平台中它是16位,而在Linux等平台中它是32位的。C99标准并没有严格规定wchar_t的位数。

4. wchar_t版函数与char版函数不对称。在C99的C标准库中,只有部分字符串函数有wchar_t版。虽然Windows平台上有A、W两套对称的API,但其他平台只有一套API。

以前我也赞同上述观点,但是现在我觉得有一个tchar.h会方便很多,理由有——

1. 方便Windows程序移植。很多控制台程序只进行了一些很简单的字符串操作,不会遇到wchar_t的缺陷。如果仅因缺少tchar.h问题而改动代码的话,那就成本太高了。

2. 无副作用。对于Linux等只有一套API的平台,可以取消UNICODE宏,这样tchar.h会将TCHAR映射为char,使用传统的窄字符串版函数。

3. 避免printf/wprintf混用时的Bug。printf与wprintf内部使用的是不同的缓冲区,混用会造成Bug。统一使用TCHAR能避免该bug。

1.3 字符串的格式控制字符问题

除了tchar.h问题外,在跨平台操作字符串时还会遇到格式控制问题。例如这些问题——

1. 在printf中使用哪种格式控制字符来输出 char字符/字符串?

2. 在printf中使用哪种格式控制字符来输出 wchar_t字符/字符串?

3. 在printf中使用哪种格式控制字符来输出 TCHAR字符/字符串?

4. 在wprintf中使用哪种格式控制字符来输出 char字符/字符串?

5. 在wprintf中使用哪种格式控制字符来输出 wchar_t字符/字符串?

6. 在wprintf中使用哪种格式控制字符来输出 TCHAR字符/字符串?

C99标准比较保守,不能完全解决上述问题。C99标准中对c、s仅存在“l”长度修正——没“l”的是char字符串,有“l”的是wchar_t字符串。详见C99标准的“7.24.2.1 The fwprintf function”。

VC++因为需要处理两套字符串API,所以它对该问题的支持非常完善。VC++中上述6个问题的答案是——

1. hc/hs。

2. lc/ls。

3. c/s。

4. hc/hs。

5. lc/ls。

6. c/s。

对于BCB、MingGW等Windows平台上的编译器,它们也兼容VC++的做法,支持这些格式控制字符。

而对于Linux等平台的gcc,它紧跟C99标准,不支持那么多格式控制字符。

1.4 _tmain入口函数问题

标准C使用main函数作为程序入口,其格式为——

int main(int argc, char* argv[])

VC++考虑到到TCHAR类型的命令行参数,于是又定义_tmain程序入口,其格式为——

int _tmain(int argc, TCHAR* argv[])

目前VC++对_tmain的支持较好,而MinGW等编译器对_tmain较差,有些只支持C标准的main。

二、解决方案

2.1 auto_tchar.h:使各种编译器兼容tchar.h

我编写了auto_tchar.h,它根据编译预处理判断该编译器是否支持tchar.h。若支持,便包含编译器的tchar.h;若不支持,则自己实现tchar.h,参考了 MinGW 的 tchar.h. http://www.mingw.org/。

在测试时发现,BCB6的tchar.h中没有定义TCHAR,只定义了_TCHAR。TCHAR是在winnt.h中定义的。于是做了如下修正——

//修正BCB6的tchar.h只有_TCHAR却没有TCHAR的问题.

#if defined(__BORLANDC__) && !defined(_TCHAR_DEFINED)typedef _TCHAR TCHAR,*PTCHAR;

typedef _TCHAR TBYTE,*PTBYTE;#define _TCHAR_DEFINED

#endif //#if defined(__BORLANDC__) && !defined(_TCHAR_DEFINED)

使用方法——

1. 将“auto_tchar.h”放在项目的include目录中。

2. 将原来的“#include ”改为“#include "auto_tchar.h"”。

2.2 prichar.h:解决字符串的格式控制字符问题

怎么解决各个编译器对格式控制字符的差异呢?

我从C99标准的inttypes.h找到了灵感。inttypes.h定义了一系列PRI开头的宏,解决了各种整数的格式控制字符问题。

我们也可以这样做,编写一个头文件,里面定义了一系列字符串的PRI宏。同时利用编译预处理判断各种编译器,定义合适的常量。

我编写了prichar.h,定义了这些宏——

SCNcA

SCNsA

SCNcW

SCNsW

SCNcT

SCNsT

PRIcA

PRIsA

PRIcW

PRIsW

PRIcT

PRIsT

前缀含义——

PRI: print, 输出.

SCN: scan, 输入.

中缀含义——

c: char, 字符.

s: string, 字符串.

后缀含义——

A: char, 窄字符版.

W: wchar_t, 宽字符版.

T: TCHAR, TCHAR版.

使用方法——

1. 将“prichar.h”放在项目的include目录中。

2. 包含该头文件(#include "prichar.h")。

3. 代码示例——

char* psa = "A汉字ABC_Welcome_歡迎_ようこそ_환영.";

wchar_t* psw = L"W汉字ABC_Welcome_歡迎_ようこそ_환영.";

TCHAR* pst = _T("T汉字ABC_Welcome_歡迎_ようこそ_환영.");

_tprintf(_T("%")_T(PRIsA)_T("\n"), psa); //输出窄字符串.

_tprintf(_T("%")_T(PRIsW)_T("\n"), psw); //输出宽字符串.

_tprintf(_T("%")_T(PRIsT)_T("\n"), pst); //输出TCHAR字符串.

注:必须多次使用“_T”宏,不能省略。如果将格式字符串写成“_T("%"PRIsA"\n")”,在编译Unicode版时,编译器将其会展开为“L"%" "hs" "\n"”,然后报告宽字符串不能与窄字符串串联错误(例如VC++报告“error C2308: 串联不匹配的字符串”)。

2.3 auto_tmain.h:解决_tmain入口函数问题

使用方法——

1. 将“auto_tmain.h”放在项目的include目录中。

2. 在主源文件包含该头文件(#include "auto_tmain.h")。

3. 现在_tmain能正常使用了(int _tmain(int argc, TCHAR* argv[]))。

三、模块源码

3.1 auto_tchar.h

全部代码——

cdec0645add3fc3c328197dda5c76203.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值