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数组的定义

?
1
2
char a[]= "Hello!" ; //defined globally
static char a[] = "Hello!" //defined as a local variable

它们都存放在程序的静态区。都需要7个字节(还有一个0终结符)。

 

宽字符

?
1
typedef unsigned short wchar_t ;

为16位的。定义宽字符及宽字符字符串的例子如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
     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!" ;

 

宽字符库函数

?
1
2
3
4
5
6
7
8
9
10
11
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,

则有

?
1
#define _tcslen wcslen

如果_UNICODE没有定义,则有

?
1
#define _tcslen strlen

同样的,TCHAR在unicode下位wchar_t,在非unicode下为char

?
1
2
3
4
5
6
//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

?
1
2
typedef char CHAR ;
typedef wchar_t WCHAR ;

 

WCHAR的匈牙利前缀为wc。

随后WINNT.H定义了6个数据类型,为指向8位字符的字符串的指针,以及四种数据类型,为指向8位字符常量字符串的指针。如下所示

?
1
2
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也定义了宽字符字符串的指针和常量字符串的指针,如下

?
1
2
typedef WCHAR * PWCHAR ,*LPWCH,*PWCH,*NWPSTR,* LPWSTR ,* PWSTR ;
typedef CONST WCHAR * LPCWCH,*PCWCH,* LPCWSTR ,* PCWSTR ;

使用TCHAR以及相关的指针类型可以把UNICODE和非UNICODE的代码统一起来,如下

?
1
2
3
4
5
6
7
8
9
#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,原因见如下代码:

?
1
2
3
4
5
6
7
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运行库提供的字符串函数外,微软自己也提供了一些变种函数。比如

?
1
2
3
4
5
6
7
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的用例如下:

 

?
1
2
3
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函数即可如下实现:

?
1
2
3
4
5
6
7
8
9
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;
}
?
1
测试之
?
1
2
3
4
5
6
7
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如下

?
1
2
3
4
5
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
参数个数可变   
标准版本sprintfswprintf_stprintf
最大长度版本_snprintf_snwprintf_sntprintf
win版本wsprintfAwsprintfWwsprintf
数组参数指针   
标准版本vsprintfvswprintf_vstprintf
最大长度版本_vsnprintf_vsnwprintf_vsntprintf
win版本wvsprintfAwvsprintfWwvsprintf

 

Formatting Message Box

下面的程序SCRNSIZE展示了如何实现一个MessageBoxPrintf函数,以接受可变数量的参数,并像printf那样格式化。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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;
}
?
1
  
?
1
  
?
1
  

该程序展示了屏幕的分辨率。这里有一个CDECL需要解释一下,和__stdcall(WINAPI)一样,都是函数的调用方式。介绍如下
1.__stdcall声明的函数被调用时,主调方负责对参数压栈,而参数出栈的任务由被调函数完成,这样,被调函数必须知道压栈参数
的个数,所以,带可变数量参数的函数不能用__stdcall声明。
2.cdecl声明的函数被调用时,主调方负责对参数的压栈,并在调用返回后,再负责参数出栈,由于主调方知道压入的参数个数,
所以被调函数可带可变数量参数。这样生成的汇编码会更多,执行程序会更大(调用函数的次数总会比较比较多的嘛)。

 

国际化

本书不涉及,参考Developing International Software for Windows 95 and Windows NT。
本书的程序将在UNICODE设置与否的情况下成功编译,普遍使用TCHAR和TEXT.并努力不要将byte和字符混淆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值