《Windows程序设计》第二章学习心得|通透说Unicode - 程序设计 - 如鹏网 ...

来源:百度文库 编辑:神马文学网 时间:2024/04/29 10:40:46
《Windows程序设计》第二章学习心得|通透说Unicode
这几天开始啃Charles Petzeld的《Windows程序设计》。啃到第二章时,为了搞清楚宽字符、UNICODE版本等这些问题,我特意分别编译了ASCII版本和UNICODE版本程序,并在虚拟机中装上Windows98,将其拖进去分别运行看效果!
这里说下我学习第二章后的一些发现和心得!
如何编译UNICODE版本程序
我们平常编译出来的程序一般都是ASCII版本。由于平时没有要求,即使我们看了书,了解了点UNICODE版本的相关知识。但因为没有尝试过动手编译,我们对于UNICODE版本的认识还是很模糊的。
不要光看不练,最好的学习方法应该付诸于实践。让我们来尝试着编译一个UNICODE版本吧!
如何下手呢?可能很多人会说:“书上说只要定义一个‘UNICODE’标识符就OK了!”嗯,没错!说起来挺容易,但是可能很多人还不清楚这个“UNICODE”标识符怎么定义,在哪里定义?
这个“UNICODE”标识符需要在源文件的最上方定义(当然,也在#include 等这些头包含预编译命令之上),即加上一句“#define UNICODE”。
这边不必顾虑“#define UNICODE”右边是否有个常量。诸如“#define UNICODE 0”,“#define UNICODE 1”或者是“#define UNICODE 1234 ”这种形式都是可以的,只要“UNICODE”这个标识符有被定义就成。搞不清楚的鹏友可以参考《C语言参考手册》或是《C语言核心技术》中关于“#ifndef”和“#ifdef”以及“#if”的相关用法。
OK,“UNICODE”标识符定义完成。
别以为这样就大功告成了。如果你尝试着去编译一下,你会失望的。编译器也许提示一堆烦人错误。别急,看一下错误报告!这时你将发现,哇,基本上都是关于类型转换的错误。不急,且往下看。
修改代码,完成UNICODE版本编译
前面杨老师在《C语言也能干大事》中一再提到,将字符串包在TEXT()宏里面。为啥?哈哈,现在终于知道啦!书中提到,UNICODE版本中,每个字符占2个字节。同时还提到,用“wchar_t”类型定义的字符数组在初始化时,需要在字符串前面加上“L”,如:
[cpp]wchar_t szRuPeng[] = L"学在如鹏!";[/cpp]
而宏TEXT,在编译UNICODE版本时将会在字符串前面加个“L”,如:
[cpp]TCHAR szRuPeng[] = TEXT("学在如鹏");[/cpp]
定义了“UNICODE”标识符后,编译器会将上面语句改成:
[cpp]wchar_t szRuPeng[] = L"学在如鹏!";[/cpp]
这个就是关键所在了,后面出现的很多类型转换问题都得基于它去解决!
之前鹏友们在学习杨老师的《C语言也能干大事》课程后,课下练习时可能会有这样的情况:把字符指针也放在TEXT()里面(这个字符指针包括指向字符串的,指向字符数值的,或者直接就是字符串数组名)。如:
[cpp]    TCHAR szRuPeng[] = "学在如鹏";
MessageBox(NULL, TEXT(szRuPeng), TEXT("如鹏"), MB_OK);
[/cpp]
不知道这样做的鹏友多不多,至少我就这么做了。
大家可能觉得这样做没什么问题,编译可以通过,程序执行似乎也完全正常。但是在编译UNCODE版本时,它却出事啦。错误报告可能是说发现未定义的标识符,怎么回事?原因在于TEXT宏会在字符串前面加个“L”,对于字符指针当然也不例外。于是就成了下面这样:
MessageBoxW(NULL, LszRuPeng, L"如鹏", MB_OK);
MessageBoxW(NULL, LszRuPeng, L"如鹏", MB_OK);
显然跑出了一个未定义标识符“LszRuPeng”。
所以,以后写代码时,除了字符串常量外,最好不要把字符指针放在TEXT()里面。推荐这样写:
[cpp]    TCHAR szRuPeng[] = TEXT("学在如鹏");
MessageBox(NULL, szRuPeng, TEXT("如鹏"), MB_OK);
[/cpp]
还有,格式化输出函数也不要放过。比如wsprintf,以前大家可能这么写:
[cpp]int iNum = 321;
TCHAR szBuff[256];
ZeroMemory(szBuff, sizeof(szBuf));
wsprintf(szBuff, "计算机%d已经改为如鹏网啦!", iNum);[/cpp]
咋一看没啥问题,同样在平时可以正常编译正常执行。可能有鹏友会说“sizeof(szBuf)应该换成sizeof(szBuf) / sizeof(TCHAR)”
嗯,当然,这也是个问题。在UNICODE版本中sizeof(TCHAR) = 2,而ZeroMemory则是对memset函数改装的一个宏。它的功能是在指定的一段连续的内存空间中填充字符'\0'。
(在MSDN中查到memset的函数原型为:
void *memset( void *dest, int c, size_t count);
从第二个参数可以看出,它即能支持ASCII版本,也能支持UNICODE版本,也就是说字符c既可以占1个字节,也可以占2个字节)
在UNICODE版本中'\0'占用2个字节。又因szBuff[256]也成了宽字符版,实际占用2 * 256 = 512个字节,故需要填充sizeof(szBuf) / sizeof(TCHAR) = 512 / 2 = 256个宽字符'\0'(编译器会自动对该字符进行零扩充)。
“OK,搞定问题!”
别高兴地太早,这里还有个更可恨的问题没有被发现。再次编译一下UNICODE版本,又报错了。还是类型转换错误,可恨的类型转换。
跟前面一样,还得把字符串放在TEXT()里面,于是就写成了下面这个德行:
wsprintf(szBuff, TEXT("计算机%d已经改为如鹏网啦!"), iNum);
wsprintf(szBuff, TEXT("计算机%d已经改为如鹏网啦!"), iNum);
是不是很别扭,可是人家就得要这样!因为wsprintf调用的是它的宽字符版即wsprintfW。于是上面的代码被编译器改成:
[cpp]int iNum = 321;
wchar_t szBuff[256];
memset(szBuff, '\0', sizeof(szBuf) / sizeof(wchar_t));
wsprintfW(szBuff, L"计算机%d已经改为如鹏网啦!", iNum);[/cpp]
又解决了一个问题, 改啊改,改啊改,没完没了地改!继续往下看!
如果有鹏友像下面这样声明一个指向字符串的指针:
[cpp]TCHAR szRuPeng[] = TEXT("学在如鹏!");
PSTR lpszRuPeng = szRuPeng;
/*或者
LPSTR lpszRuPeng = szRuPeng;
或者
PCHAR lpszRuPeng = szRuPeng;
又或者
char * lpszRuPeng = szRuPeng;
*/[/cpp]
那么真是不好意思了,又要出问题啦。类型转换问题,万恶的类型转换问题啊!!
放下仇恨把,解铃还须系铃人。这时需要改成这样:
[cpp]
TCHAR szRuPeng[] = TEXT("学在如鹏!");
PTSTR lpszRuPeng = szRuPeng;
/*或者
LPTSTR lpszRuPeng = szRuPeng;
或者
PTCHAR lpszRuPeng = szRuPeng;
又或者
TCHAR * lpszRuPeng = szRuPeng;
*/[/cpp]
为什么?书上有,翻书可以找到答案。另外,杨老师的《C语言也能干大事》也经常提到的,跳转到它们的定义去看一下吧!
我们可以想象到,诸如:
[cpp]char szRuPeng[] = "学在如鹏!";
char * lpszRuPeng = szRuPeng;
[/cpp]
这样的代码在编译UNICODE版本时虽然可以通过,但是显示的时候则会出一行乱码。
所以我们写Windows程序时,在没有特定要求下(比如char szRuPeng[] = "Study in RuPeng!";),最好使用Windows给我们提供的数据类型。如定义字符变量用TCHAR,定义字符指针用PTSTR,LPTSTR或TCHAR *,如此种种!
同样需要注意的还有字符串处理函数的使用,也最好使用Windows提供给我们的,用lstrlen替代strlen等等,这些在Charles Petzold的书中已经讲得很详细了。同时,我们在学习杨老师的《C语言也能干大事》时,已经使用过很多这样的函数了!
虽然目前我们都没有必要去编译UNICODE版本的程序。但是,在以后,也许工作上有要求,也许UNICODE有大行其道的那么一天,我们必须编写UNICODE版本的程序。那么我们原有的编程习惯将会影响到我们个人发展。
原帖由 阿修的逆袭 于 2009-11-3 18:44 发表
用了TEXT宏 貌似编译的时候会自动选择MessageBox的类型吧?
不是这样子的,举个例子吧!
写个简单的win32程序,如下:
// 这里没有定义UNICODE标识符
#include 
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR lpCmdLine,
int iShowCmd)
{
MessageBox(NULL, TEXT("学在如鹏网,在校不迷茫,毕业即辉煌!"),
TEXT("如鹏"), MB_OK);
return 0;
}
// 这里没有定义UNICODE标识符     #include int WINAPI WinMain(HINSTANCE hInstance,                    HINSTANCE hPrevInstance,                    PSTR lpCmdLine,                    int iShowCmd) {     MessageBox(NULL, TEXT("学在如鹏网,在校不迷茫,毕业即辉煌!"),                TEXT("如鹏"), MB_OK);     return 0; }
上面这段代码在编译时默认为ASCII版本,也就是说编译器会把上面的代码大致改成这样:
// 这里没有定义UNICODE标识符
#include 
int __stdcall WinMain(void *hInstance,
void *hPrevInstance,
char *lpCmdLine,
int iShowCmd)
{
//MessageBox替换为ASCII版的MessageBoxA
MessageBoxA(0, "学在如鹏网,在校不迷茫,毕业即辉煌!",
"如鹏", 0x00000000L);
// TEXT()在ASCII版里啥都没了
return 0;
}
// 这里没有定义UNICODE标识符 #include int __stdcall WinMain(void *hInstance,                       void *hPrevInstance,                       char *lpCmdLine,                       int iShowCmd) {     //MessageBox替换为ASCII版的MessageBoxA     MessageBoxA(0, "学在如鹏网,在校不迷茫,毕业即辉煌!",                 "如鹏", 0x00000000L);              // TEXT()在ASCII版里啥都没了     return 0; }
如果是定义了“UNICODE”标识符的话,也就是像如下代码:
// 下面定义了“UNICODE”标识符
#define UNICODE
#include 
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR lpCmdLine,
int iShowCmd)
{
MessageBox(NULL, TEXT("学在如鹏网,在校不迷茫,毕业即辉煌!"),
TEXT("如鹏"), MB_OK);
return 0;
}
// 下面定义了“UNICODE”标识符 #define UNICODE #include int WINAPI WinMain(HINSTANCE hInstance,                    HINSTANCE hPrevInstance,                    PSTR lpCmdLine,                    int iShowCmd) {     MessageBox(NULL, TEXT("学在如鹏网,在校不迷茫,毕业即辉煌!"),                TEXT("如鹏"), MB_OK);     return 0; }
那么编译器大致会把上面的代码改成这样:
// 下面定义了“UNICODE”标识符
#define UNICODE
#include 
int __stdcall WinMain(void *hInstance,
void *hPrevInstance,
char *lpCmdLine,
int iShowCmd)
{
// MessageBox替换为UNICODE版的MessageBoxA
MessageBoxW(0, L"学在如鹏网,在校不迷茫,毕业即辉煌!",
L"如鹏", 0x00000000L);
// 包在TEXT()里的字符串前面加上了一个“L”,表示它是宽字符版的
return 0;
}
// 下面定义了“UNICODE”标识符 #define UNICODE #include int __stdcall WinMain(void *hInstance,                       void *hPrevInstance,                       char *lpCmdLine,                       int iShowCmd) {     // MessageBox替换为UNICODE版的MessageBoxA     MessageBoxW(0, L"学在如鹏网,在校不迷茫,毕业即辉煌!",                 L"如鹏", 0x00000000L);                 // 包在TEXT()里的字符串前面加上了一个“L”,表示它是宽字符版的 return 0; }
所以,MessageBox宏被替换成哪个函数不是由TEXT决定的,而是由是否定义了“UNICODE”标识符决定的,同时它也决定了TEXT宏被替换成什么!