Kelvin的胡言乱语

==============> 重剑无锋,大巧不工。

绕死你不偿命的UNICODE、_UNICODE、__TEXT、__T、_T、_TEXT、TEXT宏

这是我在博客园的博客中的文章。

下面是原文(未大改,但删去了最后一段废话,另外,加上了文章后面回复中有水平的两个):


最近在看一些关于VC++和MFC的书时,书上对字符串的处理一般都会使用 TEXT("a string") 的形式或者 _T("a string") 的形式,自己写程序时MFC自动生成的代码中也有类似的宏。作为菜鸟,不加思考地照搬书上的 TEXT() 或者 _T() 不是我的风格,喜欢追根究底的性格促使我决定弄懂这些宏。但如果我按照以往写文章的习惯,跟着我思考的顺序来写这篇随笔的话,那是倒叙,会很不好写,所以我就按弄懂之后的正常顺序来写吧,但这也让这篇随笔看起来有点说教,各位看客且请忍受一下,谢谢。

C语言发明时尚没有UNICODE这一说,那时候米国人只有ASCII,但随着计算机的进化,程序中需要出现其它国家语言的字符,如中文,这远不是ASCII所能表示的,所以就出现了UNICODE(想深入了解UNICODE的童鞋,请猛击这里),于是C语言也新加了一个类型: wchar_t ,用于表示UNICODE字符,其定义为: #define unsigned short wchar_t ,说白了其实就是用16位双字节表示一个字符(ASCII用单字节表示一个字符)。

这样编写程序就出现了一个问题,我先定义了一个变量: char *str = "china"; 这当然没问题,但如果后来要把该字符串换成中文的,那就得换成 wchar_t *str = L"中国"; (顺便说一下,字符串前的L是告诉编译器,后面紧跟的字符串按UNICODE宽字符处理,即每个字符占两个字节)。如果字符串只有一个,改一下没问题,但很难想象一个程序中只出现一个字符串,所以,这样修改起来的工作量是很大的。

M$永远不会缺乏牛人,所以他们自然为VC++想好了解决办法,那就是 __TEXT(), __T() 等一系列宏,先来看看WinNT.h头文件,这个文件有几千行,但我们只需要抽取关键的几行出来:

#ifdef  UNICODE                     // r_winnt
#define __TEXT(quote) L##quote      // r_winnt
#else   /* UNICODE */               // r_winnt
#define __TEXT(quote) quote         // r_winnt
#endif /* UNICODE */                // r_winnt
#define TEXT(quote) __TEXT(quote)   // r_winnt

这几行代码应该很简单:如果定义了UNICODE宏,那么就将 __TEXT(quote) 宏定义为 L##quote ,如果没定义UNICODE宏,则 __TEXT(quote) 宏就是普通的 quote ,最后,再将 TEXT(quote) 宏定义为 __TEXT(quote) 宏。(如果有朋友不懂其中##符号的意思的话,这里解释一下,##起连接作用,例如 __TEXT("china") 在经过宏替换后就会被解释为L"china")

这种解决方法很巧妙,编程人员可以根据需要自由定义UNICODE宏来决定是使用ASCII字符串还是UNCODE字符串,而程序中的字符、字符串只需加个宏处理即可。与此类似的还有 __T(), _TEXT() 等宏,这些宏在tchar.h头文件里定义,这个文件同样有几千行,仍然只需要抽出关键的几行:

#ifdef  _UNICODE
#define __T(x)      L ## x
#else   /* ndef _UNICODE */
#define __T(x)      x
#endif  /* _UNICODE */
#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

其原理同上,但这里是根据_UNICODE宏(注意前面的下划线)来决定是使用ASCII字符还是UNICODE字符的。同样最后又附加了 _T()_TEXT() 宏的定义。

到此,疑问又来了,为什么要定义_UNICODE和UNICODE两个宏?M$的牛人吃多了嫌撑?要是一个宏定义了,另一个宏没定义引起冲突怎么办?于是再来查找一番,在atldef.h和afxv_w32.h两个头文件中,我找到了一模一样的内容,如下:

#ifdef _UNICODE
#ifndef UNICODE
#define UNICODE         // UNICODE is used by Windows headers
#endif
#endif

#ifdef UNICODE
#ifndef _UNICODE
#define _UNICODE        // _UNICODE is used by C-runtime/MFC headers
#endif
#endif

这段代码有点绕,但目的很清晰,就是要保证UNICODE和_UNICODE这两个宏要么都定义了,要么都未定义,不能只定义一个。所以,在上面的分析中,不论是利用UNICODE宏来定义的 __TEXT()TEXT() ,还是利用_UNICODE宏来定义的 __T()_T()_TEXT() ,都是可以正常使用的,不会出现一部分是ASCII字符串,另一部分是UNICODE字符串的低等错误。因此, __TEXT, __T, _T, _TEXT, TEXT 这几个宏的具体作用其实是一样的,没有区别,至于M$为什么对相同的功能要搞这么多奇形怪状的符号来表示,那我就不得而知了。

同时,上面的注释还解释了,UNICODE宏用于Windows头文件,而_UNICODE宏用于C运行时和MFC的头文件,当然我这个菜鸟还不太懂具体区别,只能大概猜到,在Windows的头文件中,需要根据是否使用UNICODE来定义不同版本宏的地方就使用UNICODE宏,而在MFC和标准C中,需要根据是否使用UNICODE来定义不同版本宏的地方就使用_UNICODE宏。


下面是文章后面的评论中,网友Zhenxing Zhou给出的评论:

在标准出现之前,编译器自己扩展的,一般以下划线开始,如_UNICODE,当UNICODE成为标准后,由于兼容问题,_UNICODE也应该同时定义,就出现了两个*UNICODE。

#define unsigned short wchar_t为了兼容早期不支持wchar_t的编译器。


下面是网友egmkang给出的评论:

  1. char* str = "xxx"; 这里的str编码是未知的,但是ASCII兼容的.比如说gd2312,utf-8他就是ASCII兼容的.是可以用来表示中文的.实际的编码跟文件的encoding有关系.
  2. L表示后续的字符串是Unicode编码的.但是这个Unicode编码说的很宽泛,UTF-16是Unicode,UTF-32也是Unicode.很不幸,这两种编码在C语言的实现里面同时存在.比如Windows里面用的就是UTF-16,Unix-Like呢,就是UTF-32...
  3. 马上将要到来的C1X会加强对unicode编码的支持.呵呵,但是VC应该不会支持C1X吧,因为C99到现在VC都不支持..

Comments

comments powered by Disqus