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  阅读(2187)  评论(0编辑  收藏  举报

导航