1. 前言
Windows平台有用Unicode和不用的区分:WinNT到Windows2003一直使用Unicode;WindowsCE也是如此;Win95和Win98就非如此。Windows编程对于字符使用也有各种情况:Windows API的处理方式、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++对字符透明编程
typedef unsigned short wchar_t;
从这里我们可以清楚地看到,所谓的宽字符就是无符号短整数。
L“Hello, world!”
这个L非常重要,只有带上它,编译器才知道你要将字符串存成每个字符1个字。还要注意,在L和字符串之间不能有空格。
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!
字符宏:
| 宏 | 未定义_UNICODE (ANSI字符) | 定义了_UNICODE(Unicode字符) |
| _TCHAR | char | wchar_t |
| _TSCHAR | signed char | wchar_t |
| _TUCHAR | unsigned char | wchar_t |
| _TXCHAR | char | wchar_t |
| TCHAR | char | wchar_t |
常量字符串宏:
| 宏 | 未定义_UNICODE (ANSI常量字符串) |
定义了_UNICODE (Unicode常量字符串) |
| __T(x) | x | Lx |
| _T | x | __T(x) |
| _TEXT | x | __T(x) |
* 注意,“Lx”中的“”虽然看起来很怪,却是ANSI C标准的预处理语法,它叫做“粘贴符号(token paste)”,表示将前面的L添加到宏参数上。也就是说,如果我们写了一个__T(“Software Department”),展开后即为L“Software Department”。
为了方便。可以简单地总结两条规则:
*定义字符用TCHAR
*定义常量字符串用_T
2.4.2. 透明调用字符串函数
VC++实现透明调用字符串函数也是定义了一系列宏,不过,这些宏数量太多了,没有办法把它们都列在这里,就象征性地列出一些,给大家一个印象:
| 宏 | 未定义_UNICODE (ANSI字符串函数) |
定义了_UNICODE (Unicode字符串函数) |
| _tcschr | strchr | wcschr |
| _tcscmp | strcmp | wcscmp |
| _tcslen | strlen | wcslen |
首先,Win32API中定义了若干自己的字符数据类型。所谓自己定义,无非就是用一些宏把C中的数据类型包装起来而已(确实是C的数据类型,Win32API是按C的函数调用方式定义的)。对字符数据类型的定义基本上都在 winnt.h 头文件中。
最基本的是两种字符数据类型,分别对应8位的单字节字符和16位的Unicode字符:
| 数据类型 | ANSI | UNICODE | 内部数据类型 |
| CHAR | 8位 | char | |
| WCHAR | 16位 | unsigned short | |
| PCHAR | CHAR* | char* | |
| PCH & LPCH | CHAR* | char* | |
| PSTR&NPSTR&LPSTR | CHAR* | char* | |
| PCCH&LPCCH | CONST CHAR* | const char* | |
| PCSTR&LPCSTR | CONST CHAR* | const char* | |
| PWCHAR | WCHAR* | unsigned short* | |
| PWCH&LPWCH | WCHAR* | unsigned short* | |
| PWSTR&LPWSTR&NWPSTR* | WCHAR* | unsigned short* | |
| PCWCH&LPCWCH | CONST WCHAR* | const unsigned short* | |
| PCWSTR&LPCWSTR | CONST WCHAR* | const unsigned short* |
* 注意,“NWPSTR”并没有写错,确实是“NWP”,本来我也认为应该是“NPW”(近指针)。
* 所谓的“远指针”“近指针”,在32位编程环境下已经没有意义了。
由于这些数据类型都是Windows内部分别针对ANSI和Unicode定义的,在编程中,当然要避免使用。把它们列在这里是为了方便大家参考。
//
// Neutral ANSI/UNICODE types and macros
//
#ifdef UNICODE // 以下是Unicode相关定义
#ifndef _TCHAR_DEFINED
typedef WCHAR TCHAR, *PTCHAR; // 定义基本通用类型
typedef LPWSTR LPTCH, PTCH; // 定义各种通用字符串指针
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR LPCTSTR;
typedef LPWSTR LP; // 奇怪,为什么要定义它?
#define __TEXT(quote) Lquote // 定义字符串常量宏
#else /* UNICODE */ // 以下是ANSI相关定义
#ifndef _TCHAR_DEFINED
typedef char TCHAR, *PTCHAR; // 定义基本通用类型
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字符和字串) |
| TCHAR | char | WCHAR |
| PTCHAR | char* | WCHAR* |
| PTCH&LPTCH | LPSTR | LPWSTR |
| PTSTR& LPTSTR | LPSTR | LPWSTR |
| LPCTSTR | LPCSTR | LPCWSTR |
| __TEXT( quote ) | quote | Lquote |
| TEXT( quote ) | quote | Lquote |
* 注意,LP是专门针对Unicode定义的,所以,无法用于透明编程(我不知道为什么要定义它),故未将它列在表中。
比较VC++和Windows的定义,我们可以得出如下结论:
VC++没有直接定义指针类型,Windows直接定义了指针类型;
VC++和Windows都定义了TCHAR,所以我们使用TCHAR兼容性最好;
也许我们没有必要直接使用Windows定义的指针类型,使用TCHAR*就可以了;
Windows定义的类型如此之多,可能和写Windows程序的开发组较多,标准不一有关。由于要兼容历史上的程序,只好定义多一些。我们没有必要去使用那些杂乱的定义。
然后,另一个宏根据是否定义了“UNICODE”分别展开为这两个函数:
#ifdef UNICODE
#define lstrlen lstrlenW
#else
#define lstrlen lstrlenA
#endif // !UNICODE
又是一堆函数!虽然Win32API并没有实现所有的字符串函数,我还是不愿意再记它们。我认为VC++的运行库函数就够好了。想要了解Windows函数的可以自己到MSDN里面去找。
好了,现在再来说一下COM接口和OLE中的字符类型。进行COM接口编程时,我们经常会接触OLESTR之类的东西。它们又是怎么一会事呢?大多数COM和OLE中使用的数据类型都是在basetype.h和wtypes.h中定义的。我将有关内容总结如下:
| 宏 | 未定义OLE2ANSI (Unicode字符和字串) |
定义了OLE2ANSI (ANSI字符和字串) |
| OLECHAR | WCHAR | char |
| OLESTR(x) | Lx | x |
| LPOLESTR | OLECHAR __RPC_FAR * | LPSTR |
| LPCOLESTR | const OLECHAR __RPC_FAR * | LCPSTR |
首先,我们可以看到,在定义LPOLESTR和LPCOLESTR时,利用了WIN32API的定义(那个看起来很深奥的“__RPC_FAR”,只是一个用于远程调用时规定调用约定的宏,定义在 rpc.h 头文件中)。
其次,我们看到,OLESTR之类的宏,目的也是为了实现对ANSI和UNICODE的透明编译。根据是否定义了OLE2ANSI来选择编译ANSI版本还是UNICODE版本。真不知道定义这么多宏做什么,也许是因为各个小组独立开发,各自用各自的东西的原因。
要了解COM中字符串使用更多的细节,请参考《ATL技术内幕》。
#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
#ifndef _OLEAUTO_H_
#ifdef OLE2ANSI
typedef LPSTR BSTR; // 用Windows的类型定义BSTR
#else
typedef LPWSTR BSTR; // 用Windows的类型定义BSTR
#endif
#endif
Q3:那些定义字符串常量的宏,我该用哪一个?
A3:想必大家也注意到了,为了透明转换常量字符串,WINDOWS和VC++都定义了各自的宏:
Windows:__TEXT和TEXT
VC++:__T、_T和_TEXT
这五个宏最容易让程序员迷惑了,因为它们仅仅是不加下划线,加一个下划线、两个下划线的问题。其实,只要你是开发Windows上的应用程序,用哪个宏没有什么关系。考虑到不和平台关系过于紧密,以及书写的长度,_T可能是一个比较好的选择。
对ANSI和Unicode字符的透明编程问题,尽我的了解讲了这么多,希望大家觉得有所帮助。大家如果发现有遗漏和谬误之处,请指正!
http://hi.baidu.com/kof_xi/blog/item/a240a054f63c0a1c3b2935c1.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/119272.html