windows程序设计第二章-Unicode简介
字符集历史
莫斯码,Braille盲文
American标准
1980年,the coding used on Hollerith cards
1960年,BCDIC(Binary-Coded Decimal Interchange Code)从6位扩展到8位的EBCDIC。
1950s后期,American Standard Code for Information Interchange(ASCII), 1967年完成。7位。
The World Beyond
7位ASCII的问题是,还有很多常用的符号没有包含进来,比如拉丁文,还有汉字,日文韩文等。
Extending ASCII
一个字节8位,还有128个额外的字符可以附加进来。1981年,IBM PC引入了一些声调符,小写希腊字母等。这套扩展的字符集并不是并不适合Windows。
在Windows1.0中,微软搞了套ANSI字符集,最终定为“American National Standard for Information Processing---8-Bits Single-Byte Coded Graphic Character Sets-Part 1:Latin Alphabet No 1”,简称”Latin 1”
MS-DOS 3.3 引入了code pages的概念。所谓code pages,即为字符码集到字符集的映射。最初的IBM字符集被称为code page 437,或”MS-DOS Latin US”. 而code page 850被称为”MS-DOS Latin 1”.
在MS-DOS下,当用户设置PC的键盘,视频显示器或打印机到某一个特定的code page后,字符将由该code page定义给出。但code page的不同,会给应用程序带来很大麻烦。
双字节字符集
double-byte character set(DBCS).Windows支持四种DBCS,code page 932(日文),936(简体中文),949(韩文),950(繁体中文)。
DBCS的一个问题是,ASCII字符是单字节,而其他是双字节的字符。双字节的字符由一个lead byte和trail byte组成,通常可以通过判断一个字符的第一个字节是否是lead byte,来判断它是否是一个双字节的字符。在编程中,这样会有麻烦,比如给定一个字节流,某个字符处的指针,那么该字符的前一个字符的地址是什么呢?由于无法判断前一个字符是单字节的,还是双字节的,必须我们得从字节流的开头处重新parse。
救星Unicode
16位字符。取代混乱的多个256字符的code映射,或者DBCS,Unicode是一套统一的字符系统,每个字符为16位,支持65536个字符。
Unicode的组成是这样的,前128个位ASCII(0x0000 to 0x007F),0x0080~0x00FF为ISO 8859-1的ASCII扩展,000370~0x03FF为希腊字母,0x0400~0x04FF为西里尔字母,0x0530~0x058F为亚美尼亚语 0x0590~0x05FF为希伯来语,0x3000~0x9FFF(CJK)为中日韩的字符总集。
Unicode最好的地方在于,它只有一个字符集。
Unicode的缺点是,占内存多。另外,unicode推广不力,还没有被广泛使用,也是其弱点之一。
宽字符和C语言
ANSI C(American National Standard for Programming Languages--C)支持宽字符。
ANSI C也支持多字节字符集。
宽字符不是unicode,unicode只是宽字符的一种编码(encoding)。但在本书中,意义差不多。
char 类型
char数组的定义
char a[]="Hello!";//defined globally static char a[] = "Hello!"//defined as a local variable
它们都存放在程序的静态区。都需要7个字节(还有一个0终结符)。
宽字符
typedef unsigned short wchar_t;
为16位的。定义宽字符及宽字符字符串的例子如下:
wchar_t c = 'A';//此时c为双字节值0x0041,为unicode中的字母A //如果机器是小端(least-significant bytes first) //在内存中的表示为0x41 0x00 unsigned char * cp = (unsigned char *)(&c); wchar_t b = '高';//在内存中的表示为0xDF 0xB8 cp = (unsigned char*)(&b); wchar_t * p = L"Hello!";//L让编译器知晓,字符串中的字符是宽字节字符, //一共占14个字节(最后的0终结符也占2个字节) //类似的,我们可以如下定义一个宽字符数组 static wchar_t a[] = L"Hello!";
宽字符库函数
wchar_t * pw = L"Hello!"; //如不加(const char *)强制转换,则会有编译错误 //cannot convert parameter 1 from 'wchar_t *' to 'const char *' //加上强制转换后,由于宽字节字符'H'在内存中的内容为0x48,0x00(小端),所以 //使用strlen时,结果为1,碰到了0x00,而实际上这个0x00是宽字符的一部分。 int iLength = strlen((const char *)pw); //上述例子中,运行库函数strlen是在运行时加载的,它期望的是单字节字符。 //为了支持宽字符,运行库加入了相应的函数版本。 //strlen的宽字符版本为wcslen(wide-character string length) iLength = wcslen(pw);//结果为6
printf的宽字符版本为wprintf。
维护一份源代码
只要修改一个宏,同一份源代码就可以编译成unicode版本或多字节版本。在windows中,一个解决方法是使用TCHAR,它不属于标准C,所以其中的每个函数和定义都有下划线前缀。在TCHAR.H头文件中,如果定义了_UNICODE,
则有
#define _tcslen wcslen
如果_UNICODE没有定义,则有
#define _tcslen strlen
同样的,TCHAR在unicode下位wchar_t,在非unicode下为char
//if _UNICODE defined typedef wchar_t TCHAR #define __T(x) L##x //else typedef char TCHAR #define __T(x) x
所以,建议使用_TEXT或_T宏将字符串常量包起来。其中,##是token paste
宽字符和Windows
Windows NT内部使用的是16位字符的字符串。Win98只有一小部分函数支持unicode。最好只维护一份源代码,这样可以根据实际情况编译成ascii版本或unicode版本。
Windows头文件类型
WINDOWS.H 包括一系列windows头文件
WINDEF.H 定义了windows中的很多基本类型,包含WINNT.H
WINNT.H 对unicode的支持
在WINNT.H中,首先包含了C头文件CTYPE.H,其中定义了wchar_t。然后定义了CHAR和WCHAR
typedef char CHAR; typedef wchar_t WCHAR;
WCHAR的匈牙利前缀为wc。
随后WINNT.H定义了6个数据类型,为指向8位字符的字符串的指针,以及四种数据类型,为指向8位字符常量字符串的指针。如下所示
typedef CHAR * PCHAR,*LPCH,*PCH,*NPSTR,*LPSTR,*PSTR; typedef CONST CHAR * LPCCH,*PCCH,*LPCSTR,*PCSTR;
N前缀表示”near”,L表示”long”,在Win16中表示指针大小的不同。在Win32中无区别。这里需注意,LPCH等都是类型char *。
类似的,WINNT.H也定义了宽字符字符串的指针和常量字符串的指针,如下
typedef WCHAR *PWCHAR,*LPWCH,*PWCH,*NWPSTR,*LPWSTR,*PWSTR; typedef CONST WCHAR * LPCWCH,*PCWCH,*LPCWSTR,*PCWSTR;
使用TCHAR以及相关的指针类型可以把UNICODE和非UNICODE的代码统一起来,如下
#ifndef UNICODE typedef WCHAR TCHAR,*PTCHAR; typedef LPWSTR LPTCH,PTCH,PTSTR,LPTSTR typedef LPCWSTR LPCTSTR; #else typedef char TCHAR,*PTCHAR; typedef LPSTR LPTCH,PTCH,PTSTR,LPTSTR typedef LPCSTR LPCTSTR; #endif
谨记,由于windows.h包含了很多基本的类型,在包含其他头文件时,最好现在最开始包含windows.h.
综上,有以下三点经验
1.如明确使用8位字符,请使用CHAR,PCHAR
2.如明确使用16位字符,请使用WCHAR,PWCHAR,以及加L的字符串常量
3.若依赖于UNICODE标识符定义如否,请使用TCHAR,PTCHAR和TEXT宏。
Windows函数调用
32位的Windows API其实没有MessageBox这个函数,只有MessageBoxA(ASCII版本)和MessageBoxW(宽字节版本)。但是程序员可以放心地使用MessageBox,原因见如下代码:
int WINAPI MessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT UType); int WINAPI MessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT UType) #ifndef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif
Windows的字符串函数
除了C运行库提供的字符串函数外,微软自己也提供了一些变种函数。比如
int iLength = lstrlen(pw); pString = lstrcpy(pString1,pString2); pString = lstrcpyn(pString1,pString2); pString = lstrcat(pString1,pString2); iComp = lstrcmp(pString1,pString2); iComp = lstrcmpi(pString1,pString2);
使用 printf
坏消息是,在windows程序中,无法使用printf函数,好消息是,可以使用sprintf,该函数可将格式化文本写入buffer。sprintf的用例如下:
char szBuffer[100]; sprintf(szBuffer,"The sum of %i and %i is %i",5,3,5+3); puts(szBuffer);
使用sprintf的一个麻烦之处在于,需要考虑buffer的大小。另一个win32平台的函数_snprintf解决了这个问题,它引入了表示buffer大小的参数。sprintf还有一个变形vsprintf,它只有三个参数,前两个参数与sprintf一致,第三个为指向参数数组的指针。而该指针其实是存在栈上的变量,访问这些栈上变量时,需借助于va_list,va_start,va_end等宏。
sprintf函数即可如下实现:
int MySprintf(char * szBuffer,const char * szFormat,...) { int iReturn; va_list pArgs; va_start(pArgs,szFormat); iReturn = vsprintf(szBuffer,szFormat,pArgs); va_end(pArgs); return iReturn; }
测试之
int inumber = 30; float fnumber = 90.0; char str[4] = "abc"; char szBuffer[100]; MySprintf(szBuffer,"%d %f %s",inumber,fnumber,str); puts(szBuffer); return 0;
上面的va_start,实际上即将变量szFormat后的变量地址赋予了pArgs。具体的宏va_list,va_end,va_start如下
typedef char * va_list; typedef va_start _crt_va_start #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) typedef va_end _crt_va_end #define _crt_va_end(ap) ( ap = (va_list)0 )
后来,微软又加了wsprintf和wvsprintf,可惜的是,它们不支持浮点数格式化。随着宽字符的引入,sprintf人丁兴盛,让人有点糊涂。先总结于下
ASCII | 宽字符 | Generic | |
参数个数可变 | |||
标准版本 | sprintf | swprintf | _stprintf |
最大长度版本 | _snprintf | _snwprintf | _sntprintf |
win版本 | wsprintfA | wsprintfW | wsprintf |
数组参数指针 | |||
标准版本 | vsprintf | vswprintf | _vstprintf |
最大长度版本 | _vsnprintf | _vsnwprintf | _vsntprintf |
win版本 | wvsprintfA | wvsprintfW | wvsprintf |
Formatting Message Box
下面的程序SCRNSIZE展示了如何实现一个MessageBoxPrintf函数,以接受可变数量的参数,并像printf那样格式化。
#include <Windows.h> #include <tchar.h> #include <stdio.h> int CDECL MessageBoxPrintf(TCHAR * szCaption,TCHAR * szFormat,...) { TCHAR szBuffer[1024]; va_list pArglist; va_start(pArglist,szFormat); _vsntprintf(szBuffer,sizeof(szBuffer) / sizeof(TCHAR),szFormat,pArglist); va_end(pArglist); return MessageBox(NULL,szBuffer,szCaption,0); } int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow) { int cxScreen,cyScreen; cxScreen = GetSystemMetrics(SM_CXSCREEN); cyScreen = GetSystemMetrics(SM_CYSCREEN); MessageBoxPrintf(TEXT("ScrnSize"), TEXT("The screen is %i pixels wide by %i pixels high"), cxScreen,cyScreen); return 0; }
该程序展示了屏幕的分辨率。这里有一个CDECL需要解释一下,和__stdcall(WINAPI)一样,都是函数的调用方式。介绍如下
1.__stdcall声明的函数被调用时,主调方负责对参数压栈,而参数出栈的任务由被调函数完成,这样,被调函数必须知道压栈参数
的个数,所以,带可变数量参数的函数不能用__stdcall声明。
2.cdecl声明的函数被调用时,主调方负责对参数的压栈,并在调用返回后,再负责参数出栈,由于主调方知道压入的参数个数,
所以被调函数可带可变数量参数。这样生成的汇编码会更多,执行程序会更大(调用函数的次数总会比较比较多的嘛)。
国际化
本书不涉及,参考Developing International Software for Windows 95 and Windows NT。
本书的程序将在UNICODE设置与否的情况下成功编译,普遍使用TCHAR和TEXT.并努力不要将byte和字符混淆。
posted on 2010-04-27 02:14 speedmancs 阅读(2203) 评论(0) 编辑 收藏 举报