爱问 爱问共享资料 爱问分类
首页 >IT资料 >IT书籍 >Windows核心编程-第五版(中文前六章).pdf

Windows核心编程-第五版(中文前六章).pdf举报

简介:

当前资料暂无简介!

继往开来创新高,推陈出新品佳酿 1993年,本书第1版AdvancedNT出版的时候,我和三个朋友一起成立了一个“四喜工作室”。由于四个人只有一台计算机,所以我们几个每天一睁眼,第一件事情便是抢占计算机,这台386配置简单,根本无法与现在的计算机相提并论,而且当时也没有网络,所以计算机的用途非常有限,主要也就是文字处理,玩游戏,编简单程序等,但它带给我们的乐趣至今难以忘怀。受限于当时的环境,数据和游戏的交换也基本上在圈内好友之间进行,就像搞地下活动一样约好时间地点碰头。幸运的是,由此结交了一大批计算机爱好者,后来他们大多成为IT届的领军人物。其时,从大环境看,我国网络也开始悄然起步。1993年年初,中国科学院高能物理研究所接入斯坦福大学线性加速器中心的64K专线开通,国内科学家开始在国内使用电子邮件。随后几个月的时间,金桥工程和域名体系的确立和部署,三大院校网的连接,最终将我国带入信息高速公路,推动我国IT业的迅猛发展。由此而来的便是计算机类图书和报纸期刊的炙手可热,《电脑报》等IT媒体相继崛起,计算机图书更是出现供不应求的现象,在当时,即便是国外引进翻译出版的图书,也能轻松突破几万册的销量,计算机图书的发展达到全盛时期。在这个时期,国内开发人员先后成为Jeffrey和McConell等大师的拥趸。因为在IT界,虽然资深程序员不胜枚举,但同时又是深受程序员喜爱的技术图书作家的乏善可陈。而像他们那样,曾经写过多部书,部部都引人入胜,令人醍醐灌顶,就更是凤毛麟角。他们是Windows编程世界中的中流砥柱,也是Windows技术当之无愧的布道者。曾有不少读者放言,只要是Jeffrey的书,他们必定会花时间研读,并加以收藏。这一点都不夸张,我们同时代的很多人都是在这批书的滋润下成长起来的。他们熟读了AdvancedNT之后,又如痴如狂地捧起了AdvancedWindows和ProgrammingApplicationforMicrosoftWindows等续作。他们是Jeffrey的粉丝,同时也是微软开发阵营的主力军。随着微软宣布放弃对WindowsXP以及以前版本的支持,WindowsVista的普及势在必行,迟早会安装到普通用户的计算机上。WindowsVista有很多吸引人的新特性,相信大家不用不知道,一用忘不了。(在翻译MicrosoftPress的WindowsVistaInsideOut一书的过程中,我已经深切体会到她的妙处)。作为一名程序员,有必要在第一时间适应在新的操作系统下的编程。历经15年,本书也随着Windows操作系统的“改朝换代”,升级到第5版,即WindowsviaC/C++。如果您要用C/C++开发Windows应用程序,那就不要走弯路,直接让Jeffrey告诉您如何利用Windows的新特性和新函数来编写出高效、优美的Windows应用程序。对于本书的学习,谨以《史记孔子世家》中孔子学琴一文与大家共勉(请原谅,这里引用了我另一本书的译序,其寓意深刻,忍不住又拿出来与大家分享):孔子学鼓琴师襄子,十日不进。师襄子曰:“可以益矣。”孔子曰:“丘已习其曲矣,未得其数也。”有间,曰:“已习其数,可以益矣。”孔子曰:“丘未得其志也。”有间,曰:“已习其志,可以益矣。”孔子曰:“丘未得其为人也。”有间,有所穆然深思焉,有所怡然高望而远志焉。曰:“丘得其为人,黯然而黑,几然而长,眼如望羊,如王四国,非文王其谁能为此也!”师襄子辟席再拜,曰:“师盖云文王操也。”期望读者朋友也能达到学习的三大境界:学习掌握演奏(编程)的技巧;领会其中的志趣;熟悉乐曲(程序)的作者。翻译过程中,感谢我的家人和朋友的诸多帮助和理解,尤其要感谢我的乖女儿。这个暑假,她的成长令人激赏!最后,欢迎读者指出本书的疏漏和不足之处,如果你对我翻译的部分(1~6章)有什么意见和建议,请访问我的博客(transbot.blog.163.com)指出,那里为我翻译的一些图书开辟了专栏,专门用于和读者们分享勘误和其他有用的信息。周靖Beijing2008前夕 第Ⅰ部分  程序员必读 本部分内容包括: 第1章  错误处理 第2章  字符和字符串处理 第3章  内核对象 第1章  错误处理 本章内容包括: 1.1  定义自己的错误代码 1.2  ErrorShow示例程序  在深入讨论MicrosoftWindows提供的诸多特性之前,应该先理解各个Windows函数是如何进行错误处理的。调用Windows函数时,它会先验证你传给它的参数,然后再开始执行任务。如果传入的参数无效,或者由于其他原因导致操作无法执行,则函数的返回值将指出函数在某个方面失败了。表1-1展示了大多数Windows函数使用的返回值的数据类型。表1-1常见的Windows函数返回值数据类型数据类型指出函数调用失败的值VOID这个函数不可能失败。只有极少数Windows函数的返回值类型为VOID。BOOL如果函数失败,返回值为0;否则,返回值是一个非零值。应避免测试返回值是否为TRUE;最稳妥的做法是检查它是否不为FALSE。HANDLE如果函数失败,则返回值通常为NULL;否则,HANDLE将标识一个你可以操纵的对象。请注意这种返回值,因为某些函数会返回为INVALID_HANDLE_VALUE的一个句柄值,它被定义为–1。函数的PlatformSDK文档清楚说明了函数是返回NULL还是INVALID_HANDLE_VALUE来标识失败。PVOID如果函数调用失败,返回值为NULL;否则,PVOID将标识一个数据块的内存地址。LONG/DWORD这种类型比较棘手。返回计数的函数通常会返回一个LONG或DWORD。如果函数出于某种原因不能对你想要计数的东西进行计数,它通常会返回0或–1(具体取决于函数)。如果要调用一个返回LONG/DWORD的函数,务必仔细阅读PlatformSDK文档,确保你将正确地检查可能出现的错误。如果一个Windows函数能返回错误代码,通常有助于我们理解函数调用为什么会失败。Microsoft编辑了一个列表,其中列出了所有可能的错误代码,并为每个错误代码都分配了一个32位的编号。在内部,当一个Windows函数检测到错误时,它会使用一个名为“线程本地存储区”(thread-localstorage)的机制将恰当的错误代码与“主调线程”(或者说发出调用的线程,即callingthread)关联到一起(线程本地存储区的详情将在第21章讨论)。这个机制使不同的线程能独立运行,不会出现相互干扰对方的错误代码的情况。函数返回时,其返回值会指出已发生一个错误。要查看具体是什么错误,请调用GetLastError函数:DWORDGetLastError();它的作用很简单,就是返回由上一个函数调用设置的线程的32位错误代码。有了32位错误代码之后,接着需要把它转换为更有用的信息。WinError.h头文件包含了Microsoft定义的错误代码列表。为便于你体验,下面摘录了其中的一部分://MessageId:ERROR_SUCCESS////MessageText:////Theoperationcompletedsuccessfully.//#defineERROR_SUCCESS0L#defineNO_ERROR0L//dderror#defineSEC_E_OK((HRESULT)0x00000000L)////MessageId:ERROR_INVALID_FUNCTION////MessageText:////Incorrectfunction.//#defineERROR_INVALID_FUNCTION1L//dderror////MessageId:ERROR_FILE_NOT_FOUND////MessageText:////Thesystemcannotfindthefilespecified.//#defineERROR_FILE_NOT_FOUND2L////MessageId:ERROR_PATH_NOT_FOUND////MessageText:////Thesystemcannotfindthepathspecified.//#defineERROR_PATH_NOT_FOUND3L////MessageId:ERROR_TOO_MANY_OPEN_FILES////MessageText:////Thesystemcannotopenthefile.//#defineERROR_TOO_MANY_OPEN_FILES4L////MessageId:ERROR_ACCESS_DENIED////MessageText:////Accessisdenied.//#defineERROR_ACCESS_DENIED5L可以看出,每个错误都有三种表示:一个消息ID(一个可在源代码中使用的宏,用于与GetLastError的返回值进行比较)、消息文本(描述错误的英文文本)和一个编号(应该避免使用此编号,尽量使用消息ID)。注意,我只摘录了WinError.h头文件的极小一部分,整个文件的长度超过39000行!一个Windows函数失败之后,应该马上调用GetLastError,因为假如又调用了另一个Windows函数,则此值很可能被改写。注意,成功调用的Windows函数可能用ERROR_SUCCESS改写此值。一些Windows函数调用成功可能是缘于不同的原因。例如,创建一个命名的事件内核对象时,以下两种情况均会成功:对象实际地完成创建,或者存在一个同名的事件内核对象。应用程序也许需要知道成功的原因。为返回这种信息,Microsoft选择采用“上一个错误代码”(lasterrorcode)机制。所以,当特定函数成功时,你可以调用GetLastError来确定额外的信息。对于具有这种行为的函数,PlatformSDK文档会清楚指明能以这种方式使用GetLastError。文档中提供了CreateEvent函数的一个例子;如果存在命名的事件,它会返回ERROR_ALREADY_EXISTS。调试程序时,我发现对线程的“上一个错误代码”进行监视是相当有用的。在MicrosoftVisualStudio中,Microsoft的调试器支持一个很有用的功能——你可以配置Watch窗口,让它始终显示线程的上一个错误代码和错误的文本描述。具体的做法是:在Watch窗口中选择一行,然后输入$err,hr。来看看图1-1的例子。在这个例子中,我已经调用了CreateFile函数。该函数返回值为INVALID_HANDLE_VALUE(–1)的一个HANDLE,指出它无法打开指定文件。但是Watch窗口指出,上一个错误代码(也就是调用GetLastError函数返回的错误代码)是0x00000002。多亏有了,hr限定符,Watch窗口进一步指出错误代码2是“Thesystemcannotfindthefilespecified”(系统找不到指定文件)。这就是在WinError.h头文件中为错误代码2列出的消息文本。图1-1在VisualStudio的Watch窗口中使用$err,hr来查看当前线程的“上一个错误代码”VisualStudio还搭载了一个很小的实用程序,名为ErrorLookup。利用它,可以将错误代码编号转换为相应的文本描述。如下图所示:如果我在自己写的程序中检测到一个错误,我可能希望向用户显示文本描述,而不是显示一个干巴巴的错误编号。Windows提供了一个函数,可以将错误代码转换为相应的文本描述。此函数名为FormatMessage,如下所示:DWORDFormatMessage(DWORDdwFlags,LPCVOIDpSource,DWORDdwMessageId,DWORDdwLanguageId,PTSTRpszBuffer,DWORDnSize,va_list*Arguments);FormatMessage的功能实际相当丰富,为了构造要向用户显示的字符串,它是首选的一种方式。之所以说它好用,一个原因是它能轻松地支持多种语言(译注:这里的语言是自然语言,比如汉语、英语等等,而不是计算机编程语言)。它能获取一个语言标识符作为参数,并返回那种语言的文本。当然,你首先必须翻译好字符串,并将翻译好的消息表(messagetable)资源嵌入自己的.exe或DLL模块中。但在此之后,这个函数就能自动选择正确的字符串。ErrorShow示例程序(参见后文)演示了如何调用这个函数将Microsoft定义的错误代码编号转换为相应的文本描述。经常有人问我,Microsoft是否维护着一个主控列表,其中完整列出了每个Windows函数可能返回的所有错误代码。很遗憾,答案是否定的。而且,Microsoft决不可能提供这样的列表,因为随着新的操作系统版本的产生,很难构建和维护这样的列表。这种列表的问题在于,你可以调用一个Windows函数,但在内部,这个函数可能调用另一个函数,后者又可能调用其他函数……以此类推。出于众多原因,任何一个函数都可能失败。有时,当一个函数失败时,较高级别的函数也许能够恢复,并继续执行你希望的操作。要创建这种主控列表,Microsoft必须跟踪每个函数的路径,生成所有可能的错误代码的列表。这是非常难的。而且,随着新版本的操作系统的发布,这些函数的执行路径也可能发生改变。1.1定义自己的错误代码前面讲述了Windows函数如何向其调用者指出错误。除此之外,Microsoft还允许将这种机制用于你自己的函数中。假定你要写一个供其他人调用的函数。这个函数可能会因为这样或那样的原因而失败,所以需要向调用者指出错误。为了指出错误,只需设置线程的上一个错误代码,然后令自己的函数返回FALSE,INVALID_HANDLE_VALUE、NULL或者其他合适的值。为了设置线程的上一个错误代码,只需调用以下函数,并传递你认为合适的任何32位值:VOIDSetLastError(DWORDdwErrCode);我会尽量使用WinError.h中现有的代码——只要代码能很好地反映我想报告的错误。如果WinError.h中的任何一个代码都不能准确反映一个错误,就可以创建自己的代码。错误代码是一个32位数,由表1-2描述的几个不同的字段组成。表1-2错误代码的不同字段位:31–30292827–1615–0内容严重性Microsoft/客户保留Facility代码异常代码含义0=成功1=信息(提示)2=警告3=错误0=Microsoft定义的代码1=客户定义的代码必须为0前256个值由Microsoft保留Microsoft/客户定义的代码这些字段将在第24章详细讨论。就目前来说,惟一需要注意的重要字段在位29中。Microsoft承诺,在它所生成的所有错误代码中,此位将始终为0。但是,如果要创建你自己的错误代码,就必须在此位放入一个1。通过这种方式,可以保证你的错误代码绝不会与Microsoft现在和将来定义的错误代码冲突。注意,Facility字段非常大,足以容纳4096个可能的值。其中,前256个值是为Microsoft保留的,其余的值可由你自己的应用程序来定义。1.2ErrorShow示例程序ErrorShow应用程序(01-ErrorShow.exe),演示了如何得到一个错误代码的文本描述。此应用程序的源代码和资源文件可以在本书示例代码压缩包的01-ErrorShow目录中找到,请访问http://wintellect.com/Books.aspx来下载。简单地说,这个应用程序展示了调试器的Watch窗口和ErrorLookup程序是如何工作的(参见前面的两个屏幕截图)。启动程序时,将出现以下窗口。可以在编辑控件中输入任何错误编号。单击LookUp按钮后,错误的文本描述将在对话框底部的可滚动窗口中显示。对于这个应用程序,我们惟一感兴趣的是如何调用FormatMessage。下面展示了我如何使用这个函数://GettheerrorcodeDWORDdwError=GetDlgItemInt(hwnd,IDC_ERRORCODE,NULL,FALSE);HLOCALhlocal=NULL;//Bufferthatgetstheerrormessagestring//UsethedefaultsystemlocalesincewelookforWindowsmessages//Note:thisMAKELANGIDcombinationhasavalueof0DWORDsystemLocale=MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL);//Gettheerrorcode'stextualdescriptionBOOLfOk=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_ALLOCATE_BUFFER,NULL,dwError,systemLocale,(PTSTR)&hlocal,0,NULL);if(!fOk){//Isitanetwork-relatederror?HMODULEhDll=LoadLibraryEx(TEXT("netmsg.dll"),NULL,DONT_RESOLVE_DLL_REFERENCES);if(hDll!=NULL){fOk=FormatMessage(FORMAT_MESSAGE_FROM_HMODULE|FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_ALLOCATE_BUFFER,hDll,dwError,systemLocale,(PTSTR)&hlocal,0,NULL);FreeLibrary(hDll);}}if(fOk&&(hlocal!=NULL)){SetDlgItemText(hwnd,IDC_ERRORTEXT,(PCTSTR)LocalLock(hlocal));LocalFree(hlocal);}else{SetDlgItemText(hwnd,IDC_ERRORTEXT,TEXT("Notextfoundforthiserrornumber."));第一行从编辑控件获取错误代码。然后,指向一个内存块的句柄被实例化并初始化为NULL。FormatMessage函数在内部分配内存块,并返回指向这个内存块的句柄。调用FormatMessage时,我们向它传入了FORMAT_MESSAGE_FROM_SYSTEM标志。该标志告诉FormatMessage:我们希望获得与一个系统定义的错误代码对应的字符串。另外,还传入了FORMAT_MESSAGE_ALLOCATE_BUFFER标志,要求该函数分配一个足以容纳错误文本描述的内存块。此内存块的句柄将在hlocal变量中返回。FORMAT_MESSAGE_IGNORE_INSERTS标志则允许你获得含有%占位符的消息。Windows利用它来提供上下文相关的信息,如下例所示:如果不传递这个标志,就必须为Arguments参数中的这些占位符提供具体的值。但这对于ErrorShow程序来说是不可能的,因为消息的内容事先是未知的。第三个参数指出想要查找的错误编号。第四个参数指出要用什么语言来显示文本描述。由于我们对Windows自己提供的消息感兴趣,所以语言标识符将根据两个特定的常量(即LANG_NEUTRAL和SUBLANG_NEUTRAL)来生成,这两个常量联合到一起将生成一个0值——意味着操作系统的默认语言。这种情况下,我们不能硬编码一种特定的语言,因为事先并不知道操作系统的安装语言是什么。如果FormatMessage成功,文本描述就在内存块中,我把它拷贝到对话框底部的可滚动窗口中。如果FormatMessage失败,我会尝试在NetMsg.dll模块中查找消息代码,看错误是否与网络有关(有关如何在磁盘上搜索DLL的详情,请参见第20章)。利用NetMsg.dll模块的句柄,我再一次调用FormatMessage。我们知道,每个DLL(或.exe)都可以有自己的一套错误代码。可以使用MessageCompiler(MC.exe)把这些代码添加到DLL(或.exe)模块中,并在模块中添加一个资源。VisualStudio的ErrorLookup工具允许你使用Modules对话框来完成这个操作。第2章  字符和字符串处理 本章内容字符编码ANSI和Unicode字符和字符串数据类型Windows中的Unicode和ANSI函数C运行库中的Unicode和ANSI函数C运行库中的安全字符串函数为何要用Unicode推荐的字符和字符串处理方式Unicode和ANSI字符串转换随着MicrosoftWindows在世界各地日渐流行,作为软件开发人员,将眼光投向全球市场显得越发重要。美国版本的软件在发布时间上比国际版本早6个月,这样的事情一度屡见不鲜。但是,随着操作系统的国际化支持日益增强,为国际市场发布软件产品变得越来越容易,美国版本和国际化版本的软件在发布时间上的间隔变得越来越短。Windows一如继往地为开发人员提供支持,帮助他们本地化自己的应用程序。应用程序可以通过多个函数来获得一个国家特有的信息,并能检查控制面板的设置来判断用户当前的首选项。Windows甚至能为应用程序支持不同的字体。最后一点也是非常重要的一点,WindowsVista开始提供对Unicode5.0的支持(详情参见“ExtendTheGlobalReachOfYourApplicationsWithUnicode5.0”一文,网址为http://msdn.microsoft.com/msdnmag/issues/07/01/Unicode/default.aspx)。缓冲区溢出错误(这是处理字符串时的典型错误)已成为针对应用程序乃至操作系统的各个组件发起安全攻击的媒介。这几年,Microsoft从内部和外部两个方面主动出击,倾尽全力提升Windows世界的安全水平。本章的第二部分将介绍Microsoft在C运行库中新增的函数。你应该使用这些新函数来防止应用程序在处理字符串时发生缓冲区溢出。本章的位置之所以如此靠前,是由于我极力主张你的应用程序始终使用Unicode字符串,而且始终应该通过新的安全字符串函数来处理这些字符串。如你所见,与如何安全使用Unicode字符串相关的问题在本书的每一章和每一个示例程序中都有涉及。如果有一个非Unicode的代码库,最好能将此代码库转移至Unicode,这会增强应用程序的执行性能,并为本地化工作奠定基础。另外,它还有利于同COM和.NETFramework的互操作。2.1字符编码本地化的问题就是处理不同字符集的问题。多年来,我们一直在将文本字符串编码成一组以0结尾的单字节字符。许多人对此已经习已为常。调用strlen,它会返回“以0结尾的一个ANSI单字节字符数组”中的字符数。问题是,某些语言和书写系统(例如日本汉字)的字符集有非常多的符号。一个字节最多只能表示256个符号,因此是远远不够的。为了支持这些语言和书写系统,双字节字符集(double-bytecharacterset,DBCS)应运而生。在双字节字符集中,一个字符串中的每个字符都由1个或2个字节组成。以日本汉字为例,如果第一个字符在0x81到0x9F之间,或者在0xE0到0xFC之间,就必须检查下一个字节,才能判断出一个完整的汉字。对程序员而言,和双字节字符集打交道如同一场噩梦,因为某些字符是1个字节宽,而有的字符却是2个字节宽。幸运的是,我们可以把DBCS放到一边,专心利用Windows函数和C运行库对Unicode字符串的支持。Unicode是1988年由Apple和Xerox共同建立的一项标准。1991年,成立了专门的协会来开发和推动Unicode。该协会由Apple、Compaq、Hewlett-Packard、IBM、Microsoft、Oracle、SiliconGraphics、Sybase、Unisys和Xerox等多家公司组成(协会成员的最新列表可从http://www.Unicode.org获得)。该组织负责维护Unicode标准。Unicode的完整描述可以参考Addison-Wesley出版的TheUnicodeStandard一书,该书可通过http://www.Unicode.org获得。在WindowsVista中,每个Unicode字符都使用UTF-16编码,UTF的全称是UnicodeTransformationFormat(Unicode转换格式)。UTF-16将每个字符编码为2个字节(或者说16位)。在本书中,我们在谈到Unicode时,除非专门声明,否则一般都是指UTF-16编码。Windows之所以使用UTF-16,是因为全球各地使用的大部分语言中,每个字符很容易用一个16位值来表示。这样一来,应用程序很容易遍历字符串并计算出它的长度。但是,16位不足以表示某些语言的所有字符。对于这些语言,UTF-16支持使用代理(surrogates),后者是用32位(或者说4个字节)来表示一个字符的一种方式。由于只有少数应用程序需要表示这些语言中的字符,所以UTF-16在节省空间和简化编码这两个目标之间,提供了一个很好的折衷。注意,.NETFramework始终使用UTF-16来编码所有字符和字符串,所以在你自己的Windows应用程序中,如果需要在原生代码(nativecode)和托管代码(managedcode)之间传递字符或字符串,使用UTF-16能改进性能和减少内存消耗。另外还有其他用于表示字符的UTF标准,具体如下。UTF-8UTF-8将一些字符编码为1个字节,一些字符编码为2个字节,一些字符编码为3个字节,一些字符编码为4个字节。值在0x0080以下的字符压缩为1个字节,这对美国使用的字符非常适合。0x0080和0x07FF之间的字符转换为2个字节,这对欧洲和中东地区的语言非常适用。0x0800以上的字符都转换为3个字节,适合东亚地区的语言。最后,代理对(surrogatepairs)被写为4个字节。UTF-8是一种相当流行的编码格式。但在对值为0x0800及以上的大量字符进行编码的时候,不如UTF-16高效。UTF-32UTF-32将每个字符都编码为4个字节。如果打算写一个简单的算法来遍历字符(任何语言中使用的字符),但又不想处理字节数不定的字符,这种编码方式就非常有用。例如,如果采用UTF-32编码方式,就不需要关心代理(surrogate)的问题,因为每个字符都是4个字符。显然,从内存使用这个角度来看,UTF-32并不是一种高效的编码格式。因此,很少用它将字符串保存到文件或传送到网络。这种编码格式一般在应用程序内部使用。目前,Unicode为阿拉伯语、汉语拼音、西里尔文(俄语)、希腊语、希伯来语、日语假名、朝鲜语和拉丁语(英语)字符等——这些字符称为书写符号(scripts)——定义了码位(codepoint,即一个符号在字符集中的位置)。每个版本的Unicode都在现有的书写符号的基础上引入了新的字符,甚至会引入新的书写符号,比如腓尼基文(一种古地中海文字)。字符集中还包含大量标点符号、数学符号、技术符号、箭头、装饰标志、读音符号以及其他字符。这65536个字符被划分为若干个区域,表2-1展示了部分区域以及分配到这些区域的字符。表2-1Unicode字符集和字母表(译者注:部分译法借鉴了chukl000的“統一碼5.0.0版區塊名稱表”)6位代码字符16位代码字母/书写符号0000–007FASCII0300–036F常见的变音符号0080–00FF拉丁字母补充-10400–04FF西里尔字母0100–017F欧洲拉丁字母0530–058F亚美尼亚文0180–01FF拉丁字母扩充0590–05FF希伯来文0250–02AF国际音标扩充0600–06FF阿拉伯文02B0–02FF进格修饰字母0900–097F梵文字母2.2ANSI字符和Unicode字符与字符串数据类型你知道,C语言用char数据类型来表示一个8位ANSI字符。默认情况下,在源代码中声明一个字符串时,C编译器会把字符串中的字符转换成由8位char数据类型构成的一个数组://一个8位字符charc='A';//一个数组,包含99个8位字符以及一个8位的终止0charszBuffer[100]="AString";Microsoft的C/C++编译器定义了一个内建的数据类型wchar_t,它表示一个16位的Unicode(UTF-16)字符。因为早期版本的Microsoft编译器没有提供这个内建的数据类型,所以编译器只有在指定了/Zc:wchar_t编译器开关时,才会定义这个数据类型。默认情况下,在MicrosoftVisualStudio中新建一个C++项目时,这个编译器开关是指定的。建议始终指定这个编译器开关,这样才能借助于编译器天生就能理解的内建基元类型来更好地操纵Unicode字符。注意在编译器内建对wchar_t的支持之前,有一个C头文件定义了一个wchar_t数据类型: typedefunsignedshortwchar_t;声明Unicode字符和字符串的方法如下所示://一个16位字符wchar_tc=L'A';//一个数组,包含最多99个16位字符以及一个16位的终止0wchar_tszBuffer[100]=L"AString";字符串之前的大写字母L通知编译器该字符串应当编译为一个Unicode字符串。当编译器将此字符串放入程序的数据段时,会使用UTF16来编码每个字符。在这个简单的例子中,在每个ASCII字符之间都用一个0来间隔。为了与C语言稍微有一些隔离,MicrosoftWindows团队希望定义自己的数据类型。于是,在Windows头文件WinNT.h中,定义了以下数据类型:typedefcharCHAR;//An8-bitcharactertypedefwchar_tWCHAR;//A16-bitcharacter除此之外,WinNT.h头文件还定义了一系列能为你提供大量便利的数据类型,可以用它处理字符和字符串指针://Pointerto8-bitcharacter(s)typedefCHAR*PCHAR;typedefCHAR*PSTR;typedefCONSTCHAR*PCSTR//Pointerto16-bitcharacter(s)typedefWCHAR*PWCHAR;typedefWCHAR*PWSTR;typedefCONSTWCHAR*PCWSTR;注意仔细查看WinNT.h,会看到如下定义:typedef__nullterminatedWCHAR*NWPSTR,*LPWSTR,*PWSTR;前缀__nullterminated是一个头部注解(headerannotation),它描述一个类型如何作为函数的参数和返回值使用。在VisualStudio企业版中,可以在项目属性中设置代码分析(CodeAnalysis)选项。这样会把/analyze开关添加到编译器的命令行中。这样一来,假如你的代码调用函数的方式违反了头部注解所定义的语义,编译器就会检测到这类问题。注意,只有编译器的企业版才支持这个/analyze开关。为保证本书所提供的代码的可读性,所有headerannotation都已被删除。要想更多地了解headerannotation语言,请参考MSDN文档“HeaderAnnotations”,网址是http://msdn2.microsoft.com/En-US/library/aa383701.aspx。在源代码中,具体使用哪种数据类型并不重要,但建议你尽量保持一致,以增强代码的可维护性。就我个人而言,作为Windows程序员,我会坚持使用Windows数据类型,因为这些数据类型与MSDN文档相符,有利于增强代码的可读性。另外,你在写代码的时候,可以让它使用ANSI或Unicode字符/字符串都能通过编译。WinNT.h定义了以下类型和宏:#ifdefUNICODEtypedefWCHARTCHAR,*PTCHAR,PTSTR;typedefCONSTWCHAR*PCTSTR;#define__TEXT(quote)quote//r_winnt#define__TEXT(quote)L##quote#elsetypedefCHARTCHAR,*PTCHAR,PTSTR;typedefCONSTCHAR*PCTSTR;#define__TEXT(quote)quote#endif#defineTEXT(quote)__TEXT(quote)利用这些类型和宏(少数不太常用的没有在这里列出)来写代码,无论使用ANSI还是Unicode字符,它都能通过编译。例如://如果定义了UNICODE,就是一个16位字符;否则就是一个8位字符TCHARc=TEXT('A');//如果定义了UNICODE,就是由16位字符构成的一个数组;否则就是8位字符的一个数组TCHARszBuffer[100]=TEXT("AString");2.3Windows中的Unicode和ANSI函数自WindowsNT起,Windows的所有版本都完全用Unicode来构建。也就是说,所有核心函数(创建窗口、显示文本、进行字符串处理等等)都需要Unicode字符串。调用一个Windows函数时,如果向它传入一个ANSI字符串(由单字节字符组成的一个字符串),函数首先会把字符串转换为Unicode,再把结果传给操作系统。如果希望函数返回ANSI字符串,那么操作系统会先把Unicode字符串转换为ANSI字符串,再把结果返回给你的应用程序。所有这些转换都是悄悄地进行的。当然,为了执行这些字符串转换,系统会产生时间和内存上的开销。如果一个Windows函数需要获取一个字符串作为参数,则该函数通常有两个版本。例如,一个CreateWindowEx接受Unicode字符串,另一个CreateWindowEx则接受ANSI字符串。这没错,但两个函数的原型实际是这样的:HWNDWINAPICreateWindowExW(DWORDdwExStyle,PCWSTRpClassName,//AUnicodestringPCWSTRpWindowName,//AUnicodestringDWORDdwStyle,intX,intY,intnWidth,intnHeight,HWNDhWndParent,HMENUhMenu,HINSTANCEhInstance,PVOIDpParam);HWNDWINAPICreateWindowExA(DWORDdwExStyle,PCSTRpClassName,//AnANSIstringPCSTRpWindowName,//AnANSIstringDWORDdwStyle,intX,intY,intnWidth,intnHeight,HWNDhWndParent,HMENUhMenu,HINSTANCEhInstance,PVOIDpParam);CreateWindowExW这个版本接受Unicode字符串。函数名末尾的大写字母W代表wide。Unicode字符都是16位宽,所以它们常常被称作宽(wide)字符。CreateWindowExA末尾的大写字母A表明该函数接受ANSI字符串。但在平时,我们只是在自己的代码中调用CreateWindowEx,不会直接调用CreateWindowExW或CreateWindowExA。在WinUser.h中,CreateWindowEx实际是一个宏,它的定义如下:#ifdefUNICODE#defineCreateWindowExCreateWindowExW#else#defineCreateWindowExCreateWindowExA#endif编译源代码模块时,是否定义UNICODE决定了要调用哪一个版本的CreateWindowEx。用VisualStudio创建一个新项目的时候,它默认会定义UNICODE。所以,在默认情况下,对CreateWindowEx的任何调用都会扩展宏来调用CreateWindowExW——即Unicode版本的CreateWindowEx。在WindowsVista中,CreateWindowExA的源代码只是一个转换层(translationlayer),它负责分配内存,以便将ANSI字符串转换为Unicode字符串;然后,代码会调用CreateWindowExW,并向它传递转换后的字符串。CreateWindowExW返回时,CreateWindowExA会释放它的内存缓冲区,并将窗口句柄返回给你。所以,对于要在缓冲区中填充字符串的任何函数,在你的应用程序能够处理字符串之前,系统必须先将Unicode转换为非Unicode的等价物。由于系统必须执行所有这些转换,所以应用程序需要更多内存,而且运行速度较慢。为了提高使应用程序的执行更高效,你应该一开始就用Unicode来开发程序。另外,目前已知Windows的这些转换函数中存在一些缺陷,所以避免使用它们,还有助于消除一些潜在的bug。如果是在创建供其他软件开发人员使用的动态链接库(dynamic-linklibrary,DLL),可考虑使用这种技术:在DLL中提供导出的两个函数,一个ANSI版本的,一个Unicode版本的。在ANSI版本中,只是分配内存,执行必要的字符串转换,然后调用该函数的Unicode版本。我将在本章的2.8.1节“导出ANSI和UnicodeDLL函数”演示这个过程。WindowsAPI中的一些函数(比如WinExec和OpenFile)存在的惟一目的就是提供与只支持ANSI字符串的16位Windows程序的向后兼容性。在新程序中,应避免使用这些方法。在使用WinExec和OpenFile调用的地方,应该用CreateProcess和CreateFile函数调用来代替。在内部,老函数总是会调用新函数。但老函数的最大问题在于,它们不接受Unicode字符串,而且支持的功能一般都要少一些。调用这些函数的时候,必须向其传递ANSI字符串。在WindowsVista中,大部分尚未废弃的函数都有Unicode和ANSI两个版本。然而,Microsoft逐渐开始倾向于某些函数只提供Unicode版本,比如ReadDirectoryChangesW和CreateProcessWithLogonW。Microsoft将COM从16位Windows移植到Win32时,做出了一个重要决策:所有需要字符串作为参数的COM接口方法都只接受Unicode字符串。这是一个伟大的决策,因为COM一般用于让不同的组件彼此间进行“对话”,而Unicode是传递字符串最理想的选择。在你的应用程序中全面使用Unicode,可以使它与COM的交互变得更容易。最后,当资源编译器编译完所有资源后,输出文件就是资源的一个二进制形式。资源中的字符串值(字符串表、对话框模板、菜单等等)始终都写成Unicode字符串。在WindowsVista中,如果你的应用程序没有定义UNICODE宏,操作系统将执行内部转换。例如,在编译源模块时,如果没有定义UNICODE,那么对LoadString的调用实际会调用LoadStringA函数。然后,LoadStringA读取资源中的Unicode字符串,并把它转换成ANSI形式。最后,转换为ANSI形式的字符串将从函数返回到应用程序。2.4C运行库中的Unicode函数和ANSI函数和Windows函数一样,C运行库提供了一系列函数来处理ANSI字符和字符串,并提供了另一系列函数来处理Unicode字符与字符串。然而,与Windows不同的是,ANSI版本的函数是“自力更生”的:它们不会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本。当然,Unicode版本的函数也是“自力更生”的,它们不会在内部调用ANSI版本。在C运行库中,能返回ANSI字符串长度的一个函数的例子是strlen。与之对应的是wcslen,这个C运行库函数能返回Unicode字符串的长度。这两个函数的原型都在String.h中。为了使你的源代码针对ANSI或Unicode都能编译,那么还必须包含TChar.h,该文件定义了以下宏:#ifdef_UNICODE#define_tcslenwcslen#else#define_tcslenstrlen#endif现在,在你的代码中应该调用_tcslen。如果已经定义了_UNICODE,它会扩展为wcslen;否则,它会扩展为strlen。默认情况下,在VisualStudio中新建一个C++项目时,已经定义了_UNICODE(就像已经定义了UNICODE一样)。针对不属于C++标准一部分的标识符,C运行库始终为其附加下划线前缀。但是,Windows团队没有这样做。所以,在你的应用程序中,应确保要么同时定义了UNICODE和_UNICODE,要么一个都不要定义。附录A将详细描述CmnHdr.h;本书所有示例代码都将用这个头文件来避免这种问题。2.5C运行库中的安全字符串函数任何修改字符串的函数都存在一个安全隐患:如果目标字符串缓冲区不够大,无法包含所生成的字符串,就会破坏内存中的数据(或者说发生“内存恶化”,即memorycorruption)。下面是一个例子://Thefollowingputs4charactersina//3-characterbuffer,resultinginmemorycorruptionWCHARszBuffer[3]=L"";wcscpy(szBuffer,L"abc");//Theterminating0isacharactertoo!strcpy和wcscpy函数(以及其他大多数字符串处理函数)的问题在于,它们不能接受指定了缓冲区最大长度的一个参数。所以,函数不知道自己会破坏内存。因为不知道会破坏内存,所以不会向你的代码报告错误。因为不知道出错,所以你不知道内存已经被破坏。另外,如果函数只是简单地失败,而不是破坏任何内存,那么是最理想不过的了。过去,这种行为被恶意软件肆意滥用。现在,Microsoft提供了一系列新的函数来取代C运行库的不安全的字符串处理函数(比如wcscat)。虽然多年以来,这些函数已经成为许多开发人员的老朋友,但为了写安全的代码,你应该放弃这些熟悉的、能修改字符串的C运行库函数(不过,strlen、wcslen和_tcslen等函数是没有问题的,因为它们不会修改传

相关资料推荐

用户评论(9)

0/200
  • c007136 2012-12-29 19:05:10

    这个本书,似乎不是第5版的吧

  • 10.69.3.32 2012-09-22 18:52:56

    没有想看前面的,不过还是感谢吧

  • 10.49.1.14 2012-09-04 17:17:56

    只有前六章,后面的能否也共享下

  • 10.44.7.248 2012-08-30 19:12:29

    谢谢

  • cir2633 2012-05-07 17:41:08

    很清晰,只有前六章,没有书签

资料阅读排行

该用户的其它资料

关闭

请选择举报的类型

关闭

提示

提交成功!

感谢您对爱问共享资料的支持,我们将尽快核实并处理您的举报信息。

关闭

提示

提交失败!

您的举报信息提交失败,请重试!

关闭

提示

重复举报!

亲爱的用户!感觉您对爱问共享资料的支持,请勿重复举报噢!

收藏
资料评价:

所需积分:0 立即下载
返回
顶部
举报
资料
关闭

温馨提示

感谢您对爱问共享资料的支持,精彩活动将尽快为您呈现,敬请期待!