VC++ Windows平台字符透明编程大总结

来源:互联网 发布:java 数组遍历方式 编辑:程序博客网 时间:2024/06/05 21:56

 

1. 前言

Windows平台有用Unicode和不用的区分:WinNT到Windows2003一直使用Unicode,WindowsCE也是如此;Win95和Win98就非如此。

Windows编程对于字符使用也有各种情况:WindowsAPI的处理方式、MFC的处理方式、VC++的处理方式、COM的处理方式。

本文对所有这些方式作了一个总结,期望程序员能够以本文为引子,找到各种情况下处理字符透明编程的方法。


所谓的字符透明编程,主要针对Unicode和ANSI字符。

本来Unicode是比较简单的一个东西,说起来一个Unicode字符就是一个无符号短整数而已(16位,2个字节),

但是,我相信大多数VC++程序员都有这样的困惑:VC++和Win32API中那些用来实现ANSI和Unicode透明编程的,

样子长得很像的宏,都在哪儿定义的?它们之间的关系如何?

这就要求我们了解编程平台和操作系统支持ANSI和Unicode透明编程的方法。
具体来说,就是要了解VC++的运行库和Win32API是如何解决该问题的。

更进一步,我们还应该了解COM解决该问题的方式。最后,由于许多VC++程序员使用MFC框架进行编程,了解MFC框架处理该问题的方法也有必要。

本文内容在《ATL技术内幕》的第二章也有比较详细的讲述,尤其是关于COM的内容。但是,该书没有讲VC++相关内容;也没有将内容整理得更清晰

一些(尤其是没有给出各种情况下的表格以供查找)。所以,本文将重点放在VC++和Windows针对字符透明编程采用方法的归纳和比较上。
本文可以作为一个出发点,有了本文介绍的基础之后,想要更多地了解BSTR的细节,可以看《ATL技术内幕》;想要更多地了解针对字符透明编程问题,可以看其他的相关资料。
// -------------------------------------------------------------------------------------------------------------------------------------------------------

2. VC++对字符透明编程

首先要说的是,对宽字符的支持其实是ANSI C标准的一部分,用以支持多字节表示一个字符。

宽字符和Unicode并不是一回事,Unicode只是宽字符能支持的一种编码方式。

但是,由于我们现在主要考虑Unicode,不妨把这两种东西当作同义。

2.1. 宽字符的定义


在ANSI中,一个字符(char)的长度为一个字节(Byte)。使用Unicode时,一个字符应该占据一个字(Word)。

VC++在wchar.h头文件中定义了最基本的宽字符类型wchar_t:

typedef unsigned short wchar_t;

从这里我们可以清楚地看到,所谓的宽字符就是无符号短整数。

2.2. 常量宽字符串

对C++程序员而言,构造字符串常量是一项经常性的工作。那么,如何构造宽字符字符串常量呢?

很简单,只要在字符串常量前加上一个大写的L就可以了,比如:
  L“Hello, world!”
这个L非常重要,只有带上它,编译器才知道你要将字符串存成每个字符1个字。还要注意,在L和字符串之间不能有空格。

2.3. 宽字符串库函数

为了操作宽字符串,VC++专门定义了一套函数,比如,求宽字符串长度的函数是
size_t  __cdel  wchlen ( const  wchar_t * )

为什么要专门定义这些函数呢?最根本的原因是,ANSI下的字符串都是以“\0”来标识字符串尾的,许多字符串函数的正确操作均以此为基础进行。

而我们知道,在宽字符的情况下,一个字符在内存中要占据一个字的空间,这就会使操作ANSI字符的字符串函数无法正确操作。

以“Hello”字符串为例,在宽字符下,它的五个字符是:


0x0048   0x0065   0x006c   0x006c   0x006f

在内存中,实际的排列是:
48 00 65 00 6c 00 6c 00 6f 00

于是,ANSI字符串函数,如 strlen,在碰到第一个48后的00时,就会认为字符串到尾了,用 strlen 对宽字符串求长度的结果就永远会是1!

2.4. 用宏实现对ANSI和Unicode的透明编程

看到这儿,想必程序员都会感到沮丧:“完了,两套字符串函数!”不用说,针对ANSI字符和Unicode字符维护两套代码是令人讨厌的事情

。就算是自己在一套代码中写一些预编译语句执行条件编译,也是非常麻烦的事,因为要用字符串的地方实在是太多了。为了减轻大家的编程负担

,VC++定义了一系列的宏,帮助实现对ANSI和Unicode的透明编程。从上面的讨论我们可以看到,要做的工作是两个:

 

一,透明定义字符和常量字符串;

二,透明调用字符串函数。

 

下面就分别讲用于这两个方面的宏。

2.4.1. 透明定义字符和常量字符串

该工作主要是由 tchar.h 头文件中定义的若干宏完成的,根据“_UNICODE”(注意,有下划线)定义与否,这些宏展开为ANSI或Unicode字符(字符串)。

有兴趣者可以去查看头文件,这里我做了如下归纳:
字符宏:

未定义_UNICODE (ANSI字符)定义了_UNICODE(Unicode字符)_TCHARcharwchar_t_TSCHARsigned char wchar_t_TUCHARunsigned charwchar_t  _TXCHARcharwchar_tTCHARcharwchar_t

 


常量字符串宏:

未定义_UNICODE
(ANSI常量字符串)
定义了_UNICODE
(Unicode常量字符串)
__T(x)xL##x_Tx__T(x)_TEXT x __T(x)


* 注意,“L##x”中的“##”虽然看起来很怪,却是ANSIC标准的预处理语法,它叫做“粘贴符号(tokenpaste)”,表示将前面的L添加到宏参数上。

也就是说,如果我们写了一个__T(“SoftwareDepartment”),展开后即为L“Software Department”。
为了方便。可以简单地总结两条规则:

  • 定义字符用TCHAR  
  • 定义常量字符串用_T


2.4.2. 透明调用字符串函数

VC++实现透明调用字符串函数也是定义了一系列宏,不过,这些宏数量太多了,没有办法把它们都列在这里,就象征性地列出一些,给大家一个印象:

未定义_UNICODE
(ANSI字符串函数)
定义了_UNICODE
(Unicode字符串函数)
_tcschr strchr wcschr_tcscmpstrcmpwcscmp_tcslen strlenwcslen


我给大家的建议是:当你需要具体的某个函数的时候,最好去查 tchar.h,或者查某些专门讲VC++运行库函数的书。
好了,VC++的东西大致就是这样,下面讲Windows的处理方法。
// -------------------------------------------------------------------------------------------------------------------------------------------------------

3. Win32API对字符透明编程


3.1. Win32API定义的数据类型

首先,Win32API中定义了若干自己的字符数据类型。所谓自己定义,无非就是用一些宏把C中的数据类型包装起来而已

确实是C的数据类型,Win32API是按C的函数调用方式定义的)。对字符数据类型的定义基本上都在 winnt.h 头文件中。
最基本的是两种字符数据类型,分别对应8位的单字节字符和16位的Unicode字符:


typedef  char                 CHAR
typedef  unsigned short  WCHAR


另外,Windows还定义了6种8位字符串指针、6种16位字符串指针、4种8位常量字符串指针、4种16位常量字符串指针。这里归纳如下:

数据类型ANSIUNICODE内部数据类型CHAR8位char WCHAR 16位unsigned short PCHARCHAR*char* PCH & LPCH CHAR*char* PSTR&NPSTR&LPSTRCHAR* char*PCCH&LPCCH CONST CHAR*const char*PCSTR&LPCSTR CONST CHAR* const char* PWCHAR WCHAR* unsigned short* PWCH&LPWCHWCHAR*unsigned short* PWSTR&LPWSTR&NWPSTR* WCHAR* unsigned short* PCWCH&LPCWCHCONST WCHAR* const unsigned short*PCWSTR&LPCWSTRCONST WCHAR* const unsigned short*
  • 注意,“NWPSTR”并没有写错,确实是“NWP”,本来我也认为应该是“NPW”(近指针)。
  • 所谓的“远指针”“近指针”,在32位编程环境下已经没有意义了。

由于这些数据类型都是Windows内部分别针对ANSI和Unicode定义的,在编程中,当然要避免使用。把它们列在这里是为了方便大家参考。

3.2. Win32API中对ANSI和Unicode的透明编程

我们还是从两个方面来讲:透明定义字符和常量字符串;透明调用字符串函数。

3.2.1. 透明定义字符和常量字符串

这里把winnt.h 头文件中实现透明定义字符和常量字符串的部分摘出来,相信对大家的理解会有所帮助(加黑部分定义了通用字符定义):

    

//// Neutral ANSI/UNICODE types and macros//#ifdef     UNICODE                       // 以下是Unicode相关定义#ifndef    _TCHAR_DEFINEDtypedef    WCHAR   TCHAR,   *PTCHAR;     // 定义基本通用类型#define    _TCHAR_DEFINED#endif     /* !_TCHAR_DEFINED */typede    LPWSTR   LPTCH,   PTCH;        // 定义各种通用字符串指针typedef   LPWSTR   PTSTR,   LPTSTR;typedef   LPCWSTR  LPCTSTR;typedef   LPWSTR   LP;                   // 奇怪,为什么要定义它?#define   __TEXT(quote)     L##quote     // 定义字符串常量宏#else     /* UNICODE */                  // 以下是ANSI相关定义#ifndef    _TCHAR_DEFINEDtypedef    char    TCHAR,   *PTCHAR;     // 定义基本通用类型#define    _TCHAR_DEFINED#endif     /* !_TCHAR_DEFINED */typedef    LPSTR   LPTCH,   PTCH;        // 定义各种通用字符串指针typedef    LPSTR   PTSTR,   LPTSTR;typedef    LPCSTR  LPCTSTR;#define    __TEXT(quote)    quote        // 定义字符串常量宏#endif     /* UNICODE */   #define    TEXT(quote)      __TEXT(quote)// 定义另一个字符串常量宏

    

 

从这段程序我们可以看出,winnt.h不过就是根据是否定义了UNICODE(没有下划线),利用Windows内部定义的数据类型进行一个条件编译。
下面,用表格的形式将以上内容做一个总结,以方便大家查阅:

未定义UNICODE
(ANSI字符和字串)
定义了UNICODE
(Unicode字符和字串)
TCHARcharWCHARPTCHARchar*WCHAR*PTCH&LPTCHLPSTRLPWSTR
PTSTR& LPTSTRLPSTRLPWSTRLPCTSTRLPCSTRLPCWSTR__TEXT( quote )quoteL##quote
TEXT( quote )quote

L##quote


* 注意,LP是专门针对Unicode定义的,所以,无法用于透明编程(我不知道为什么要定义它),故未将它列在表中。
比较VC++和Windows的定义,我们可以得出如下结论:
   VC++没有直接定义指针类型,Windows直接定义了指针类型;
   VC++和Windows都定义了TCHAR,所以我们使用TCHAR兼容性最好;
   也许我们没有必要直接使用Windows定义的指针类型,使用TCHAR*就可以了;
   Windows定义的类型如此之多,可能和写Windows程序的开发组较多,标准不一有关。由于要兼容历史上的程序,只好定义多一些。我们没有必要去使用那些杂乱的定义。

3.2.2. 透明调用字符串函数

Win32API中又定义了一套字符串函数,总的来说,它的解决方法是,对ANSI和Unicode字符,分别定义了不同的函数(在Kernel32.dll中实现)。比如,求字符串长度的函数,就分别是:
WINBASEAPI  int  WINAPI  lstrlenA ( LPCSTR lpString );
WINBASEAPI  int  WINAPI  lstrlenW( LPCWSTR lpString );
然后,另一个宏根据是否定义了“UNICODE”分别展开为这两个函数:
#ifdef UNICODE
#define lstrlen  lstrlenW
#else
#define lstrlen  lstrlenA
#endif // !UNICODE
又是一堆函数!虽然Win32API并没有实现所有的字符串函数,我还是不愿意再记它们。我认为VC++的运行库函数就够好了。想要了解Windows函数的可以自己到MSDN里面去找。
// -------------------------------------------------------------------------------------------------------------------------------------------------------

4. COM对字符透明编程

好了,现在再来说一下COM接口和OLE中的字符类型。进行COM接口编程时,我们经常会接触OLESTR之类的东西。它们又是怎么一会事呢?大多数COM和OLE中使用的数据类型都是在basetype.h和wtypes.h中定义的。我将有关内容总结如下:

未定义OLE2ANSI
(Unicode字符和字串)
定义了OLE2ANSI
(ANSI字符和字串)
OLECHARWCHARcharOLESTR(x)L##xxLPOLESTR OLECHAR __RPC_FAR *LPSTRLPCOLESTRconst OLECHAR __RPC_FAR *LCPSTR


首先,我们可以看到,在定义LPOLESTR和LPCOLESTR时,利用了WIN32API的定义(那个看起来很深奥的“__RPC_FAR”,只是一个用于远程调用时规定调用约定的宏,定义在 rpc.h 头文件中)。
其次,我们看到,OLESTR之类的宏,目的也是为了实现对ANSI和UNICODE的透明编译。根据是否定义了OLE2ANSI来选择编译ANSI版本还是UNICODE版本。真不知道定义这么多宏做什么,也许是因为各个小组独立开发,各自用各自的东西的原因。
要了解COM中字符串使用更多的细节,请参考《ATL技术内幕》。
// -------------------------------------------------------------------------------------------------------------------------------------------------------

5. MFC对字符透明编程

最后,讲讲MFC如何处理该问题。MFC的处理很简单,它只是利用了VC++运行库的处理方式。具体来说,就是从Afxw_32.h头文件的152行开始,有这么几句:
#ifndef _INC_TCHAR
#include <tchar.h>     // 该头文件包含了VC++字符串透明编程所需内容
#endif
#ifdef _MBCS
#ifndef _INC_MBCTYPE
#include <mbctype.h>
#endif
#ifndef _INC_MBSTRING
#include <mbstring.h>
#endif
#endif
也就是说,在MFC中,我们应该使用VC++的字符串透明编程方式。
但是,要注意的是,对于BSTR这个宏(该宏主要在Oleauto.h头文件中声明的自动化接口的函数中使用),MFC根据OLE2ANSI是否被定义有其不同展开(在Afx.h)中:
#ifndef _OLEAUTO_H_
#ifdef OLE2ANSI
typedef LPSTR BSTR; // 用Windows的类型定义BSTR
#else
typedef LPWSTR BSTR; // 用Windows的类型定义BSTR
#endif
#endif
// -------------------------------------------------------------------------------------------------------------------------------------------------------

6. 常见问题(Q&A)

最后,根据我的体会讲一些大家有可能感到迷惑的问题(当然,也许大家认为这些问题都很简单)。
Q1:VC++的处理方式和Win32API的处理方式,我该用哪一个?
A1:这两种方法的关系是这样的:Win32API给出的是操作系统内带的支持,而VC++可以被看做是与操作系统相关性较小的一种方式(虽然微软做了很多扩展,把它搞得和操作系统有了不少相关性)。所以,如果你要考虑程序以后在各平台上的移植,还是少用操作系统内部直接支持的东西为妙。
Q2:我们什么时候定义_UNICODE或是UNICODE?
A2:调整Project Setting时。按Alt+F7,选择“C/C++”标签,将“_UNICODE”或“UNICODE”添加在“Preprocessor definitions”编辑框中就可以了。
Q3:那些定义字符串常量的宏,我该用哪一个?
A3:想必大家也注意到了,为了透明转换常量字符串,WINDOWS和VC++都定义了各自的宏:
Windows:__TEXT和TEXT
VC++:__T、_T和_TEXT
这五个宏最容易让程序员迷惑了,因为它们仅仅是不加下划线,加一个下划线、两个下划线的问题。其实,只要你是开发Windows上的应用程序,用哪个宏没有什么关系。考虑到不和平台关系过于紧密,以及书写的长度,_T可能是一个比较好的选择。
Q4:我如何在Debug状态下观察Unicode字符串的值?
A4:一般说来,如果你把一个Unicode字符串变量名拖到 Watch 窗口中,你看到的是一个32位16进制值,也就是说,是一个指针的地址。而我们想看到的是内存中存的字符串。怎么办?你在这个变量名后面加上“, su”就可以了。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 家里明线太难看怎么办 个人停交社保怎么办 电话话筒有问题怎么办 电话听筒有问题怎么办 中学毕业证丢了怎么办 外地户口上高中怎么办 数学思维反应慢怎么办 不小心逆行了怎么办 格力手机老卡顿怎么办? 华为3g手机充不上电怎么办 外地上成都牌照怎么办 小汽车牌照坏了怎么办 买车上郑州牌照怎么办 汽车前牌照丢失怎么办 有人套我车牌怎么办 在本地怎么办外地车牌 二手车卖了车牌怎么办 科目四预约失败怎么办 车子过户了车牌怎么办 检车没有保险怎么办 异地超速12分怎么办 驾驶证考试过期了怎么办 买了库存车怎么办 车龄长了油耗高怎么办 新车发现补过漆怎么办 魅蓝note3内存不足怎么办 汽车安全检测证怎么办 a证被扣12分怎么办 突发事作后事故单位怎么办 如果遇到突发事故怎么办? 班级遇到突发事故怎么办 高速上出车祸怎么办? 安卓车载中控大屏卡怎么办 车辆交通信息卡怎么办 成都焊工压力容器证怎么办 天车钢丝绳绞住怎么办 受伤了怎么办安全教案 复读后学籍档案怎么办 开车就是开不好怎么办 货车提不了档案怎么办 二手车档案丢了怎么办