`
ruilin215
  • 浏览: 1148577 次
  • 性别: Icon_minigender_2
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

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

阅读更多

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


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++对字符透明编程


首先要说的是,对宽字符的支持其实是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字符)
_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 L##x
_T x __T(x)
_TEXT x __T(x)

* 注意,“L##x”中的“##”虽然看起来很怪,却是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

我给大家的建议是:当你需要具体的某个函数的时候,最好去查 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位常量字符串指针。这里归纳如下:

数据类型 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定义的,在编程中,当然要避免使用。把它们列在这里是为了方便大家参考。


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


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


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


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

//
// Neutral ANSI/UNICODE types and macros
//
#ifdef UNICODE // 以下是Unicode相关定义
#ifndef _TCHAR_DEFINED
typedef WCHAR TCHAR, *PTCHAR;// 定义基本通用类型

#define _TCHAR_DEFINED
#endif /* !_TCHAR_DEFINED */

typedef LPWSTR LPTCH, PTCH;// 定义各种通用字符串指针
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR LPCTSTR;
typedef LPWSTR LP;// 奇怪,为什么要定义它?
#define __TEXT(quote) L##quote // 定义字符串常量宏
#else /* UNICODE */ // 以下是ANSI相关定义
#ifndef _TCHAR_DEFINED
typedef 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字符和字串)
TCHAR char WCHAR
PTCHAR char* WCHAR*
PTCH&LPTCH LPSTR LPWSTR
PTSTR& LPTSTR LPSTR LPWSTR
LPCTSTR LPCSTR LPCWSTR
__TEXT( quote ) quote L##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字符和字串)
OLECHAR WCHAR char
OLESTR(x) L##x 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技术内幕》。


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”就可以了。

对ANSI和Unicode字符的透明编程问题,尽我的了解讲了这么多,希望大家觉得有所帮助。大家如果发现有遗漏和谬误之处,请指正!

分享到:
评论

相关推荐

    Windows VC++系统编程.doc

    书中详细阐述了如何使用VC++环境和Windows API来实现各种功能,从基本的窗口创建到复杂的系统级操作,对于任何希望深入理解Windows平台编程的开发者来说,都是宝贵的参考资料。通过学习这本书,读者不仅可以掌握...

    VC++6.0实效编程百例

    4·设置并叠加透明图片 5·颜色渐变进度条 6·透明窗体 第二章 7·调色程序 8·颜色下拉框 9·模拟拷贝进程 10·通用对话框 11·窗体分割 12·实现QQ程序的抽屉效果 13·以动画方式弹出,关闭窗口 14·半透明窗体 15...

    vc++高级界面编程

    1. **MFC(Microsoft Foundation Classes)框架**:VC++中的界面编程通常基于MFC库,它是一个C++类库,为Windows应用程序提供了一套面向对象的API。学习如何使用MFC创建窗口、对话框和控件是高级界面编程的基础。 2...

    VC++编程,鼠标移动显示坐标

    在VC++编程环境中,开发一个能够实时显示鼠标位置坐标的程序是一项基础且实用的任务,尤其对于图形用户界面(GUI)的开发和调试至关重要。本文将深入探讨如何利用Microsoft Visual C++来实现这一功能,主要涉及的...

    VC++ 6.0下透明ListBox控件类

    在Windows编程中,VC++ 6.0是一个经典的开发环境,用于创建桌面应用程序。在这个环境中,我们经常需要处理各种控件,如ListBox,以提供用户交互。本篇将深入探讨如何在VC++ 6.0下实现一个透明的ListBox控件类,让...

    VC++数字时钟

    在计算机编程领域,创建一个数字...总的来说,"VC++数字时钟"是一个实用且有趣的编程练习,它涵盖了基础的Windows编程和C++编程技巧,同时也可以作为进一步探索高级编程技术的起点,如多线程、网络通信、数据库集成等。

    vc++游戏编程指南

    ### vc++游戏编程指南知识点概览 #### 一、游戏类型与对应的编程重点 ##### 1. 2D游戏 **特点**: 视角固定或仅有四个观察方向,特效较少。 - **推荐阅读**: 第1、2、3、4、5章及第12章的相关部分。 - **可选阅读**...

    vc++图形技术大全

    《VC++图形技术大全》是一本深入探讨使用Visual C++进行图形编程的综合教程,涵盖了广泛的主题,旨在帮助开发者掌握各种高级图形处理技巧。通过学习这些知识点,你可以创建丰富的交互式应用程序,包括图像处理、游戏...

    vc++编写的界面源代码

    在IT行业中,VC++(Visual C++)是一种广泛使用的编程工具,尤其在开发Windows桌面应用程序时。本资源“vc++编写的界面源代码”提供了一套完全自绘的界面源码,这对于学习和理解界面编程的开发者来说是一份非常宝贵...

    vc++ 应用源码包_1

    基于MFC和STL平台的字符串类,可以实现在快速字符串搜索。 enum_display_modes_demo.zip enum_display_modes_src.zip 列出所有的显示模式并列表出来,通过单击列表来改变显示分辨率。 iconbutton_demo.zip ...

    VC++使用ADO开发ACCESS数据库

    在Windows编程中,Visual C++(VC++)是一种强大的开发工具,尤其在处理数据库应用时。本教程将重点介绍如何利用ActiveX Data Objects(ADO)来连接和操作Microsoft Access数据库。ADO是Microsoft的数据访问接口,它...

    透明加解密客户和服务端源VC++

    3. **VC++**:这是一种由微软开发的面向对象的C++编程环境,用于创建Windows桌面应用程序、游戏、服务器软件等。在这个项目中,VC++可能被用来编写客户端和服务端的加密解密逻辑。 4. **透明**:这通常指的是加密...

    VC++高级界面特效

    在IT行业中,VC++(Visual C++)是一种广泛使用的编程环境,由Microsoft开发,用于创建Windows平台上的桌面应用程序。在“VC++高级界面特效”这个主题中,我们主要探讨的是如何利用VC++来构建具有独特视觉效果和用户...

    让你的VC++软件界面更漂亮-界面

    4. Aero Glass效果:如果你的系统支持Windows Vista及以上版本,可以利用Aero Glass API实现半透明效果,为界面增添现代感。这需要对DWM(Desktop Window Manager)有深入理解,但结果会非常吸引人。 5. 创新的布局...

    vc++ 应用源码包_6

    基于MFC和STL平台的字符串类,可以实现在快速字符串搜索。 enum_display_modes_demo.zip enum_display_modes_src.zip 列出所有的显示模式并列表出来,通过单击列表来改变显示分辨率。 iconbutton_demo.zip ...

    VC++常用函数

    在VC++中获取本地IP地址可以通过调用Windows API函数实现。常见的方法是使用`WSAStartup`初始化网络库,然后使用`gethostbyname`或`gethostbyaddr`函数来获取主机信息,并从中解析出IP地址。 --- #### VC调用CHM...

    VC++使用GDI控制png图片半透明制作漂亮时钟.rar

    在VC++编程环境中,利用GDI(Graphics Device Interface)来处理PNG图像并实现半透明效果,可以创建出具有视觉吸引力的漂亮时钟应用。本文将深入探讨如何通过GDI技术来实现这一目标。 首先,PNG图像格式支持Alpha...

    vc++对话框超炫界面

    在VC++编程环境中,开发具有超炫界面的应用程序可以极大地提升用户体验,使你的软件在众多同类产品中脱颖而出。本文将深入探讨如何利用VC++来创建独具特色的对话框界面,并分享一些实用技巧。 首先,理解MFC...

    vc++ 应用源码包_5

    基于MFC和STL平台的字符串类,可以实现在快速字符串搜索。 enum_display_modes_demo.zip enum_display_modes_src.zip 列出所有的显示模式并列表出来,通过单击列表来改变显示分辨率。 iconbutton_demo.zip ...

Global site tag (gtag.js) - Google Analytics