关闭

关闭

封号提示

内容

首页 Windows核心编程-第五版(中文).pdf

Windows核心编程-第五版(中文).pdf

Windows核心编程-第五版(中文).pdf

上传者: 泥巴 2010-01-25 评分1 评论9 下载6w+次 收藏0 阅读量6w+ 暂无简介 简介 举报

简介:本文档为《Windows核心编程-第五版(中文)pdf》,可适用于IT/计算机领域,主题内容包含继往开来创新高推陈出新品佳酿 年本书第版AdvancedNT出版的时候我和三个朋友一起成立了一个“四喜工作室”。由于四个人只有一台计算机所以我们几个符等。

继往开来创新高推陈出新品佳酿 年本书第版AdvancedNT出版的时候我和三个朋友一起成立了一个“四喜工作室”。由于四个人只有一台计算机所以我们几个每天一睁眼第一件事情便是抢占计算机这台配置简单根本无法与现在的计算机相提并论而且当时也没有网络所以计算机的用途非常有限主要也就是文字处理玩游戏编简单程序等但它带给我们的乐趣至今难以忘怀。受限于当时的环境数据和游戏的交换也基本上在圈内好友之间进行就像搞地下活动一样约好时间地点碰头。幸运的是由此结交了一大批计算机爱好者后来他们大多成为IT届的领军人物。其时从大环境看我国网络也开始悄然起步。年年初中国科学院高能物理研究所接入斯坦福大学线性加速器中心的K专线开通国内科学家开始在国内使用电子邮件。随后几个月的时间金桥工程和域名体系的确立和部署三大院校网的连接最终将我国带入信息高速公路推动我国IT业的迅猛发展。由此而来的便是计算机类图书和报纸期刊的炙手可热《电脑报》等IT媒体相继崛起计算机图书更是出现供不应求的现象在当时即便是国外引进翻译出版的图书也能轻松突破几万册的销量计算机图书的发展达到全盛时期。在这个时期国内开发人员先后成为Jeffrey和McConell等大师的拥趸。因为在IT界虽然资深程序员不胜枚举但同时又是深受程序员喜爱的技术图书作家的乏善可陈。而像他们那样曾经写过多部书部部都引人入胜令人醍醐灌顶就更是凤毛麟角。他们是Windows编程世界中的中流砥柱也是Windows技术当之无愧的布道者。曾有不少读者放言只要是Jeffrey的书他们必定会花时间研读并加以收藏。这一点都不夸张我们同时代的很多人都是在这批书的滋润下成长起来的。他们熟读了AdvancedNT之后又如痴如狂地捧起了AdvancedWindows和ProgrammingApplicationforMicrosoftWindows等续作。他们是Jeffrey的粉丝同时也是微软开发阵营的主力军。随着微软宣布放弃对WindowsXP以及以前版本的支持WindowsVista的普及势在必行迟早会安装到普通用户的计算机上。WindowsVista有很多吸引人的新特性相信大家不用不知道一用忘不了。(在翻译MicrosoftPress的WindowsVistaInsideOut一书的过程中我已经深切体会到她的妙处)。作为一名程序员有必要在第一时间适应在新的操作系统下的编程。历经年本书也随着Windows操作系统的“改朝换代”升级到第版即WindowsviaCC。如果您要用CC开发Windows应用程序那就不要走弯路直接让Jeffrey告诉您如何利用Windows的新特性和新函数来编写出高效、优美的Windows应用程序。对于本书的学习谨以《史记孔子世家》中孔子学琴一文与大家共勉(请原谅这里引用了我另一本书的译序其寓意深刻忍不住又拿出来与大家分享):孔子学鼓琴师襄子十日不进。师襄子曰:“可以益矣。”孔子曰:“丘已习其曲矣未得其数也。”有间曰:“已习其数可以益矣。”孔子曰:“丘未得其志也。”有间曰:“已习其志可以益矣。”孔子曰:“丘未得其为人也。”有间有所穆然深思焉有所怡然高望而远志焉。曰:“丘得其为人黯然而黑几然而长眼如望羊如王四国非文王其谁能为此也!”师襄子辟席再拜曰:“师盖云文王操也。”期望读者朋友也能达到学习的三大境界:学习掌握演奏(编程)的技巧领会其中的志趣熟悉乐曲(程序)的作者。翻译过程中感谢我的家人和朋友的诸多帮助和理解尤其要感谢我的乖女儿。这个暑假她的成长令人激赏!最后欢迎读者指出本书的疏漏和不足之处如果你对我翻译的部分(~章)有什么意见和建议请访问我的博客(transbotblogcom)指出那里为我翻译的一些图书开辟了专栏专门用于和读者们分享勘误和其他有用的信息。周靖Beijing前夕 第Ⅰ部分  程序员必读 本部分内容包括: 第章  错误处理 第章  字符和字符串处理 第章  内核对象 第章  错误处理 本章内容包括:   定义自己的错误代码   ErrorShow示例程序  在深入讨论MicrosoftWindows提供的诸多特性之前应该先理解各个Windows函数是如何进行错误处理的。调用Windows函数时它会先验证你传给它的参数然后再开始执行任务。如果传入的参数无效或者由于其他原因导致操作无法执行则函数的返回值将指出函数在某个方面失败了。表展示了大多数Windows函数使用的返回值的数据类型。表常见的Windows函数返回值数据类型数据类型指出函数调用失败的值VOID这个函数不可能失败。只有极少数Windows函数的返回值类型为VOID。BOOL如果函数失败返回值为否则返回值是一个非零值。应避免测试返回值是否为TRUE最稳妥的做法是检查它是否不为FALSE。HANDLE如果函数失败则返回值通常为否则HANDLE将标识一个你可以操纵的对象。请注意这种返回值因为某些函数会返回为INVALIDHANDLEVALUE的一个句柄值它被定义为–。函数的PlatformSDK文档清楚说明了函数是返回还是INVALIDHANDLEVALUE来标识失败。PVOID如果函数调用失败返回值为否则PVOID将标识一个数据块的内存地址。LONGDWORD这种类型比较棘手。返回计数的函数通常会返回一个LONG或DWORD。如果函数出于某种原因不能对你想要计数的东西进行计数它通常会返回或–(具体取决于函数)。如果要调用一个返回LONGDWORD的函数务必仔细阅读PlatformSDK文档确保你将正确地检查可能出现的错误。如果一个Windows函数能返回错误代码通常有助于我们理解函数调用为什么会失败。Microsoft编辑了一个列表其中列出了所有可能的错误代码并为每个错误代码都分配了一个位的编号。在内部当一个Windows函数检测到错误时它会使用一个名为“线程本地存储区”(threadlocalstorage)的机制将恰当的错误代码与“主调线程”(或者说发出调用的线程即callingthread)关联到一起(线程本地存储区的详情将在第章讨论)。这个机制使不同的线程能独立运行不会出现相互干扰对方的错误代码的情况。函数返回时其返回值会指出已发生一个错误。要查看具体是什么错误请调用GetLastError函数:DWORDGetLastError()它的作用很简单就是返回由上一个函数调用设置的线程的位错误代码。有了位错误代码之后接着需要把它转换为更有用的信息。WinErrorh头文件包含了Microsoft定义的错误代码列表。为便于你体验下面摘录了其中的一部分:MessageId:ERRORSUCCESSMessageText:Theoperationcompletedsuccessfully#defineERRORSUCCESSL#defineNOERRORLdderror#defineSECEOK((HRESULT)xL)MessageId:ERRORINVALIDFUNCTIONMessageText:Incorrectfunction#defineERRORINVALIDFUNCTIONLdderrorMessageId:ERRORFILENOTFOUNDMessageText:Thesystemcannotfindthefilespecified#defineERRORFILENOTFOUNDLMessageId:ERRORPATHNOTFOUNDMessageText:Thesystemcannotfindthepathspecified#defineERRORPATHNOTFOUNDLMessageId:ERRORTOOMANYOPENFILESMessageText:Thesystemcannotopenthefile#defineERRORTOOMANYOPENFILESLMessageId:ERRORACCESSDENIEDMessageText:Accessisdenied#defineERRORACCESSDENIEDL可以看出每个错误都有三种表示:一个消息ID(一个可在源代码中使用的宏用于与GetLastError的返回值进行比较)、消息文本(描述错误的英文文本)和一个编号(应该避免使用此编号尽量使用消息ID)。注意我只摘录了WinErrorh头文件的极小一部分整个文件的长度超过行!一个Windows函数失败之后应该马上调用GetLastError因为假如又调用了另一个Windows函数则此值很可能被改写。注意成功调用的Windows函数可能用ERRORSUCCESS改写此值。一些Windows函数调用成功可能是缘于不同的原因。例如创建一个命名的事件内核对象时以下两种情况均会成功:对象实际地完成创建或者存在一个同名的事件内核对象。应用程序也许需要知道成功的原因。为返回这种信息Microsoft选择采用“上一个错误代码”(lasterrorcode)机制。所以当特定函数成功时你可以调用GetLastError来确定额外的信息。对于具有这种行为的函数PlatformSDK文档会清楚指明能以这种方式使用GetLastError。文档中提供了CreateEvent函数的一个例子如果存在命名的事件它会返回ERRORALREADYEXISTS。调试程序时我发现对线程的“上一个错误代码”进行监视是相当有用的。在MicrosoftVisualStudio中Microsoft的调试器支持一个很有用的功能你可以配置Watch窗口让它始终显示线程的上一个错误代码和错误的文本描述。具体的做法是:在Watch窗口中选择一行然后输入$err,hr。来看看图的例子。在这个例子中我已经调用了CreateFile函数。该函数返回值为INVALIDHANDLEVALUE(–)的一个HANDLE指出它无法打开指定文件。但是Watch窗口指出上一个错误代码(也就是调用GetLastError函数返回的错误代码)是x。多亏有了,hr限定符Watch窗口进一步指出错误代码是“Thesystemcannotfindthefilespecified”(系统找不到指定文件)。这就是在WinErrorh头文件中为错误代码列出的消息文本。图在VisualStudio的Watch窗口中使用$err,hr来查看当前线程的“上一个错误代码”VisualStudio还搭载了一个很小的实用程序名为ErrorLookup。利用它可以将错误代码编号转换为相应的文本描述。如下图所示:如果我在自己写的程序中检测到一个错误我可能希望向用户显示文本描述而不是显示一个干巴巴的错误编号。Windows提供了一个函数可以将错误代码转换为相应的文本描述。此函数名为FormatMessage如下所示:DWORDFormatMessage(DWORDdwFlags,LPCVOIDpSource,DWORDdwMessageId,DWORDdwLanguageId,PTSTRpszBuffer,DWORDnSize,valist*Arguments)FormatMessage的功能实际相当丰富为了构造要向用户显示的字符串它是首选的一种方式。之所以说它好用一个原因是它能轻松地支持多种语言(译注:这里的语言是自然语言比如汉语、英语等等而不是计算机编程语言)。它能获取一个语言标识符作为参数并返回那种语言的文本。当然你首先必须翻译好字符串并将翻译好的消息表(messagetable)资源嵌入自己的exe或DLL模块中。但在此之后这个函数就能自动选择正确的字符串。ErrorShow示例程序(参见后文)演示了如何调用这个函数将Microsoft定义的错误代码编号转换为相应的文本描述。经常有人问我Microsoft是否维护着一个主控列表其中完整列出了每个Windows函数可能返回的所有错误代码。很遗憾答案是否定的。而且Microsoft决不可能提供这样的列表因为随着新的操作系统版本的产生很难构建和维护这样的列表。这种列表的问题在于你可以调用一个Windows函数但在内部这个函数可能调用另一个函数后者又可能调用其他函数……以此类推。出于众多原因任何一个函数都可能失败。有时当一个函数失败时较高级别的函数也许能够恢复并继续执行你希望的操作。要创建这种主控列表Microsoft必须跟踪每个函数的路径生成所有可能的错误代码的列表。这是非常难的。而且随着新版本的操作系统的发布这些函数的执行路径也可能发生改变。定义自己的错误代码前面讲述了Windows函数如何向其调用者指出错误。除此之外Microsoft还允许将这种机制用于你自己的函数中。假定你要写一个供其他人调用的函数。这个函数可能会因为这样或那样的原因而失败所以需要向调用者指出错误。为了指出错误只需设置线程的上一个错误代码然后令自己的函数返回FALSEINVALIDHANDLEVALUE、或者其他合适的值。为了设置线程的上一个错误代码只需调用以下函数并传递你认为合适的任何位值:VOIDSetLastError(DWORDdwErrCode)我会尽量使用WinErrorh中现有的代码只要代码能很好地反映我想报告的错误。如果WinErrorh中的任何一个代码都不能准确反映一个错误就可以创建自己的代码。错误代码是一个位数由表描述的几个不同的字段组成。表错误代码的不同字段位:–––内容严重性Microsoft客户保留Facility代码异常代码含义=成功=信息(提示)=警告=错误=Microsoft定义的代码=客户定义的代码必须为前个值由Microsoft保留Microsoft客户定义的代码这些字段将在第章详细讨论。就目前来说惟一需要注意的重要字段在位中。Microsoft承诺在它所生成的所有错误代码中此位将始终为。但是如果要创建你自己的错误代码就必须在此位放入一个。通过这种方式可以保证你的错误代码绝不会与Microsoft现在和将来定义的错误代码冲突。注意Facility字段非常大足以容纳个可能的值。其中前个值是为Microsoft保留的其余的值可由你自己的应用程序来定义。ErrorShow示例程序ErrorShow应用程序(ErrorShowexe)演示了如何得到一个错误代码的文本描述。此应用程序的源代码和资源文件可以在本书示例代码压缩包的ErrorShow目录中找到请访问http:wintellectcomBooksaspx来下载。简单地说这个应用程序展示了调试器的Watch窗口和ErrorLookup程序是如何工作的(参见前面的两个屏幕截图)。启动程序时将出现以下窗口。可以在编辑控件中输入任何错误编号。单击LookUp按钮后错误的文本描述将在对话框底部的可滚动窗口中显示。对于这个应用程序我们惟一感兴趣的是如何调用FormatMessage。下面展示了我如何使用这个函数:GettheerrorcodeDWORDdwError=GetDlgItemInt(hwnd,IDCERRORCODE,,FALSE)HLOCALhlocal=BufferthatgetstheerrormessagestringUsethedefaultsystemlocalesincewelookforWindowsmessagesNote:thisMAKELANGIDcombinationhasavalueofDWORDsystemLocale=MAKELANGID(LANGNEUTRAL,SUBLANGNEUTRAL)Gettheerrorcode'stextualdescriptionBOOLfOk=FormatMessage(FORMATMESSAGEFROMSYSTEM|FORMATMESSAGEIGNOREINSERTS|FORMATMESSAGEALLOCATEBUFFER,,dwError,systemLocale,(PTSTR)hlocal,,)if(!fOk){IsitanetworkrelatederrorHMODULEhDll=LoadLibraryEx(TEXT("netmsgdll"),,DONTRESOLVEDLLREFERENCES)if(hDll!=){fOk=FormatMessage(FORMATMESSAGEFROMHMODULE|FORMATMESSAGEIGNOREINSERTS|FORMATMESSAGEALLOCATEBUFFER,hDll,dwError,systemLocale,(PTSTR)hlocal,,)FreeLibrary(hDll)}}if(fOk(hlocal!=)){SetDlgItemText(hwnd,IDCERRORTEXT,(PCTSTR)LocalLock(hlocal))LocalFree(hlocal)}else{SetDlgItemText(hwnd,IDCERRORTEXT,TEXT("Notextfoundforthiserrornumber"))第一行从编辑控件获取错误代码。然后指向一个内存块的句柄被实例化并初始化为。FormatMessage函数在内部分配内存块并返回指向这个内存块的句柄。调用FormatMessage时我们向它传入了FORMATMESSAGEFROMSYSTEM标志。该标志告诉FormatMessage:我们希望获得与一个系统定义的错误代码对应的字符串。另外还传入了FORMATMESSAGEALLOCATEBUFFER标志要求该函数分配一个足以容纳错误文本描述的内存块。此内存块的句柄将在hlocal变量中返回。FORMATMESSAGEIGNOREINSERTS标志则允许你获得含有占位符的消息。Windows利用它来提供上下文相关的信息如下例所示:如果不传递这个标志就必须为Arguments参数中的这些占位符提供具体的值。但这对于ErrorShow程序来说是不可能的因为消息的内容事先是未知的。第三个参数指出想要查找的错误编号。第四个参数指出要用什么语言来显示文本描述。由于我们对Windows自己提供的消息感兴趣所以语言标识符将根据两个特定的常量(即LANGNEUTRAL和SUBLANGNEUTRAL)来生成这两个常量联合到一起将生成一个值意味着操作系统的默认语言。这种情况下我们不能硬编码一种特定的语言因为事先并不知道操作系统的安装语言是什么。如果FormatMessage成功文本描述就在内存块中我把它拷贝到对话框底部的可滚动窗口中。如果FormatMessage失败我会尝试在NetMsgdll模块中查找消息代码看错误是否与网络有关(有关如何在磁盘上搜索DLL的详情请参见第章)。利用NetMsgdll模块的句柄我再一次调用FormatMessage。我们知道每个DLL(或exe)都可以有自己的一套错误代码。可以使用MessageCompiler(MCexe)把这些代码添加到DLL(或exe)模块中并在模块中添加一个资源。VisualStudio的ErrorLookup工具允许你使用Modules对话框来完成这个操作。第章  字符和字符串处理 本章内容字符编码ANSI和Unicode字符和字符串数据类型Windows中的Unicode和ANSI函数C运行库中的Unicode和ANSI函数C运行库中的安全字符串函数为何要用Unicode推荐的字符和字符串处理方式Unicode和ANSI字符串转换随着MicrosoftWindows在世界各地日渐流行作为软件开发人员将眼光投向全球市场显得越发重要。美国版本的软件在发布时间上比国际版本早个月这样的事情一度屡见不鲜。但是随着操作系统的国际化支持日益增强为国际市场发布软件产品变得越来越容易美国版本和国际化版本的软件在发布时间上的间隔变得越来越短。Windows一如继往地为开发人员提供支持帮助他们本地化自己的应用程序。应用程序可以通过多个函数来获得一个国家特有的信息并能检查控制面板的设置来判断用户当前的首选项。Windows甚至能为应用程序支持不同的字体。最后一点也是非常重要的一点WindowsVista开始提供对Unicode的支持(详情参见“ExtendTheGlobalReachOfYourApplicationsWithUnicode”一文网址为http:msdnmicrosoftcommsdnmagissuesUnicodedefaultaspx)。缓冲区溢出错误(这是处理字符串时的典型错误)已成为针对应用程序乃至操作系统的各个组件发起安全攻击的媒介。这几年Microsoft从内部和外部两个方面主动出击倾尽全力提升Windows世界的安全水平。本章的第二部分将介绍Microsoft在C运行库中新增的函数。你应该使用这些新函数来防止应用程序在处理字符串时发生缓冲区溢出。本章的位置之所以如此靠前是由于我极力主张你的应用程序始终使用Unicode字符串而且始终应该通过新的安全字符串函数来处理这些字符串。如你所见与如何安全使用Unicode字符串相关的问题在本书的每一章和每一个示例程序中都有涉及。如果有一个非Unicode的代码库最好能将此代码库转移至Unicode这会增强应用程序的执行性能并为本地化工作奠定基础。另外它还有利于同COM和NETFramework的互操作。字符编码本地化的问题就是处理不同字符集的问题。多年来我们一直在将文本字符串编码成一组以结尾的单字节字符。许多人对此已经习已为常。调用strlen它会返回“以结尾的一个ANSI单字节字符数组”中的字符数。问题是某些语言和书写系统(例如日本汉字)的字符集有非常多的符号。一个字节最多只能表示个符号因此是远远不够的。为了支持这些语言和书写系统双字节字符集(doublebytecharactersetDBCS)应运而生。在双字节字符集中一个字符串中的每个字符都由个或个字节组成。以日本汉字为例如果第一个字符在x到xF之间或者在xE到xFC之间就必须检查下一个字节才能判断出一个完整的汉字。对程序员而言和双字节字符集打交道如同一场噩梦因为某些字符是个字节宽而有的字符却是个字节宽。幸运的是我们可以把DBCS放到一边专心利用Windows函数和C运行库对Unicode字符串的支持。Unicode是年由Apple和Xerox共同建立的一项标准。年成立了专门的协会来开发和推动Unicode。该协会由Apple、Compaq、HewlettPackard、IBM、Microsoft、Oracle、SiliconGraphics、Sybase、Unisys和Xerox等多家公司组成(协会成员的最新列表可从http:wwwUnicodeorg获得)。该组织负责维护Unicode标准。Unicode的完整描述可以参考AddisonWesley出版的TheUnicodeStandard一书该书可通过http:wwwUnicodeorg获得。在WindowsVista中每个Unicode字符都使用编码的全称是UnicodeTransformationFormat(Unicode转换格式)。将每个字符编码为个字节(或者说位)。在本书中我们在谈到Unicode时除非专门声明否则一般都是指编码。Windows之所以使用是因为全球各地使用的大部分语言中每个字符很容易用一个位值来表示。这样一来应用程序很容易遍历字符串并计算出它的长度。但是位不足以表示某些语言的所有字符。对于这些语言支持使用代理(surrogates)后者是用位(或者说个字节)来表示一个字符的一种方式。由于只有少数应用程序需要表示这些语言中的字符所以在节省空间和简化编码这两个目标之间提供了一个很好的折衷。注意NETFramework始终使用来编码所有字符和字符串所以在你自己的Windows应用程序中如果需要在原生代码(nativecode)和托管代码(managedcode)之间传递字符或字符串使用能改进性能和减少内存消耗。另外还有其他用于表示字符的标准具体如下。将一些字符编码为个字节一些字符编码为个字节一些字符编码为个字节一些字符编码为个字节。值在x以下的字符压缩为个字节这对美国使用的字符非常适合。x和xFF之间的字符转换为个字节这对欧洲和中东地区的语言非常适用。x以上的字符都转换为个字节适合东亚地区的语言。最后代理对(surrogatepairs)被写为个字节。是一种相当流行的编码格式。但在对值为x及以上的大量字符进行编码的时候不如高效。将每个字符都编码为个字节。如果打算写一个简单的算法来遍历字符(任何语言中使用的字符)但又不想处理字节数不定的字符这种编码方式就非常有用。例如如果采用编码方式就不需要关心代理(surrogate)的问题因为每个字符都是个字符。显然从内存使用这个角度来看并不是一种高效的编码格式。因此很少用它将字符串保存到文件或传送到网络。这种编码格式一般在应用程序内部使用。目前Unicode为阿拉伯语、汉语拼音、西里尔文(俄语)、希腊语、希伯来语、日语假名、朝鲜语和拉丁语(英语)字符等这些字符称为书写符号(scripts)定义了码位(codepoint即一个符号在字符集中的位置)。每个版本的Unicode都在现有的书写符号的基础上引入了新的字符甚至会引入新的书写符号比如腓尼基文(一种古地中海文字)。字符集中还包含大量标点符号、数学符号、技术符号、箭头、装饰标志、读音符号以及其他字符。这个字符被划分为若干个区域表展示了部分区域以及分配到这些区域的字符。表Unicode字符集和字母表(译者注:部分译法借鉴了chukl的“統一碼版區塊名稱表”)位代码字符位代码字母书写符号–FASCII–F常见的变音符号–FF拉丁字母补充–FF西里尔字母–F欧洲拉丁字母–F亚美尼亚文–FF拉丁字母扩充–FF希伯来文–AF国际音标扩充–FF阿拉伯文B–FF进格修饰字母–F梵文字母ANSI字符和Unicode字符与字符串数据类型你知道C语言用char数据类型来表示一个位ANSI字符。默认情况下在源代码中声明一个字符串时C编译器会把字符串中的字符转换成由位char数据类型构成的一个数组:一个位字符charc='A'一个数组包含个位字符以及一个位的终止charszBuffer="AString"Microsoft的CC编译器定义了一个内建的数据类型wchart它表示一个位的Unicode()字符。因为早期版本的Microsoft编译器没有提供这个内建的数据类型所以编译器只有在指定了Zc:wchart编译器开关时才会定义这个数据类型。默认情况下在MicrosoftVisualStudio中新建一个C项目时这个编译器开关是指定的。建议始终指定这个编译器开关这样才能借助于编译器天生就能理解的内建基元类型来更好地操纵Unicode字符。注意在编译器内建对wchart的支持之前有一个C头文件定义了一个wchart数据类型: typedefunsignedshortwchart声明Unicode字符和字符串的方法如下所示:一个位字符wchartc=L'A'一个数组包含最多个位字符以及一个位的终止wchartszBuffer=L"AString"字符串之前的大写字母L通知编译器该字符串应当编译为一个Unicode字符串。当编译器将此字符串放入程序的数据段时会使用来编码每个字符。在这个简单的例子中在每个ASCII字符之间都用一个来间隔。为了与C语言稍微有一些隔离MicrosoftWindows团队希望定义自己的数据类型。于是在Windows头文件WinNTh中定义了以下数据类型:typedefcharCHARAnbitcharactertypedefwchartWCHARAbitcharacter除此之外WinNTh头文件还定义了一系列能为你提供大量便利的数据类型可以用它处理字符和字符串指针:Pointertobitcharacter(s)typedefCHAR*PCHARtypedefCHAR*PSTRtypedefCONSTCHAR*PCSTRPointertobitcharacter(s)typedefWCHAR*PWCHARtypedefWCHAR*PWSTRtypedefCONSTWCHAR*PCWSTR注意仔细查看WinNTh会看到如下定义:typedefterminatedWCHAR*NWPSTR,*LPWSTR,*PWSTR前缀terminated是一个头部注解(headerannotation)它描述一个类型如何作为函数的参数和返回值使用。在VisualStudio企业版中可以在项目属性中设置代码分析(CodeAnalysis)选项。这样会把analyze开关添加到编译器的命令行中。这样一来假如你的代码调用函数的方式违反了头部注解所定义的语义编译器就会检测到这类问题。注意只有编译器的企业版才支持这个analyze开关。为保证本书所提供的代码的可读性所有headerannotation都已被删除。要想更多地了解headerannotation语言请参考MSDN文档“HeaderAnnotations”网址是http:msdnmicrosoftcomEnUSlibraryaaaspx。在源代码中具体使用哪种数据类型并不重要但建议你尽量保持一致以增强代码的可维护性。就我个人而言作为Windows程序员我会坚持使用Windows数据类型因为这些数据类型与MSDN文档相符有利于增强代码的可读性。另外你在写代码的时候可以让它使用ANSI或Unicode字符字符串都能通过编译。WinNTh定义了以下类型和宏:#ifdefUNICODEtypedefWCHARTCHAR,*PTCHAR,PTSTRtypedefCONSTWCHAR*PCTSTR#defineTEXT(quote)quoterwinnt#defineTEXT(quote)L##quote#elsetypedefCHARTCHAR,*PTCHAR,PTSTRtypedefCONSTCHAR*PCTSTR#defineTEXT(quote)quote#endif#defineTEXT(quote)TEXT(quote)利用这些类型和宏(少数不太常用的没有在这里列出)来写代码无论使用ANSI还是Unicode字符它都能通过编译。例如:如果定义了UNICODE就是一个位字符否则就是一个位字符TCHARc=TEXT('A')如果定义了UNICODE就是由位字符构成的一个数组否则就是位字符的一个数组TCHARszBuffer=TEXT("AString")Windows中的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字符都是位宽所以它们常常被称作宽(wide)字符。CreateWindowExA末尾的大写字母A表明该函数接受ANSI字符串。但在平时我们只是在自己的代码中调用CreateWindowEx不会直接调用CreateWindowExW或CreateWindowExA。在WinUserh中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。如果是在创建供其他软件开发人员使用的动态链接库(dynamiclinklibraryDLL)可考虑使用这种技术:在DLL中提供导出的两个函数一个ANSI版本的一个Unicode版本的。在ANSI版本中只是分配内存执行必要的字符串转换然后调用该函数的Unicode版本。我将在本章的节“导出ANSI和UnicodeDLL函数”演示这个过程。WindowsAPI中的一些函数(比如WinExec和OpenFile)存在的惟一目的就是提供与只支持ANSI字符串的位Windows程序的向后兼容性。在新程序中应避免使用这些方法。在使用WinExec和OpenFile调用的地方应该用CreateProcess和CreateFile函数调用来代替。在内部老函数总是会调用新函数。但老函数的最大问题在于它们不接受Unicode字符串而且支持的功能一般都要少一些。调用这些函数的时候必须向其传递ANSI字符串。在WindowsVista中大部分尚未废弃的函数都有Unicode和ANSI两个版本。然而Microsoft逐渐开始倾向于某些函数只提供Unicode版本比如ReadDirectoryChangesW和CreateProcessWithLogonW。Microsoft将COM从位Windows移植到Win时做出了一个重要决策:所有需要字符串作为参数的COM接口方法都只接受Unicode字符串。这是一个伟大的决策因为COM一般用于让不同的组件彼此间进行“对话”而Unicode是传递字符串最理想的选择。在你的应用程序中全面使用Unicode可以使它与COM的交互变得更容易。最后当资源编译器编译完所有资源后输出文件就是资源的一个二进制形式。资源中的字符串值(字符串表、对话框模板、菜单等等)始终都写成Unicode字符串。在WindowsVista中如果你的应用程序没有定义UNICODE宏操作系统将执行内部转换。例如在编译源模块时如果没有定义UNICODE那么对LoadString的调用实际会调用LoadStringA函数。然后LoadStringA读取资源中的Unicode字符串并把它转换成ANSI形式。最后转换为ANSI形式的字符串将从函数返回到应用程序。C运行库中的Unicode函数和ANSI函数和Windows函数一样C运行库提供了一系列函数来处理ANSI字符和字符串并提供了另一系列函数来处理Unicode字符与字符串。然而与Windows不同的是ANSI版本的函数是“自力更生”的:它们不会把字符串转换为Unicode形式再从内部调用函数的Unicode版本。当然Unicode版本的函数也是“自力更生”的它们不会在内部调用ANSI版本。在C运行库中能返回ANSI字符串长度的一个函数的例子是strlen。与之对应的是wcslen这个C运行库函数能返回Unicode字符串的长度。这两个函数的原型都在Stringh中。为了使你的源代码针对ANSI或Unicode都能编译那么还必须包含TCharh该文件定义了以下宏:#ifdefUNICODE#definetcslenwcslen#else#definetcslenstrlen#endif现在在你的代码中应该调用tcslen。如果已经定义了UNICODE它会扩展为wcslen否则它会扩展为strlen。默认情况下在VisualStudio中新建一个C项目时已经定义了UNICODE(就像已经定义了UNICODE一样)。针对不属于C标准一部分的标识符C运行库始终为其附加下划线前缀。但是Windows团队没有这样做。所以在你的应用程序中应确保要么同时定义了UNICODE和UNICODE要么一个都不要定义。附录A将详细描述CmnHdrh本书所有示例代码都将用这个头文件来避免这种问题。C运行库中的安全字符串函数任何修改字符串的函数都存在一个安全隐患:如果目标字符串缓冲区不够大无法包含所生成的字符串就会破坏内存中的数据(或者说发生“内存恶化”即memorycorruption)。下面是一个例子:Thefollowingputscharactersinacharacterbuffer,resultinginmemorycorruptionWCHARszBuffer=L""wcscpy(szBuffer,L"abc")Theterminatingisacharactertoo!strcpy和wcscpy函数(以及其他大多数字符串处理函数)的问题在于它们不能接受指定了缓冲区最大长度的一个参数。所以函数不知道自己会破坏内存。因为不知道会破坏内存所以不会向你的代码报告错误。因为不知道出错所以你不知道内存已经被破坏。另外如果函数只是简单地失败而不是破坏任何内存那么是最理想不过的了。过去这种行为被恶意软件肆意滥用。现在Microsoft提供了一系列新的函数来取代C运行库的不安全的字符串处理函数(比如wcscat)。虽然多年以来这些函数已经成为许多开发人员的老朋友但为了写安全的代码你应该放弃这些熟悉的、能修改字符串的C运行库函数(不过strlen、wcslen和tcslen等函数是没有问题的因为它们不会修改传

类似资料

该用户的其他资料

shell经典教程.pdf

Linux操作系统下C语言编程入门.pdf

Linux平台上学C语言.pdf

鸟哥私房菜(全).pdf

RFC2616(HTTP)中文版.pdf

职业精品

精彩专题

结婚彩礼真有那么重要吗?

原创于西周而后沿袭至今的彩礼,虽然被一部分家长奉为圭臬,但越来越多的年轻人对结婚必须要彩礼不以为然。彩礼引发的社会矛盾越来越受到关注,结婚是自己的事,如人饮水冷暖自知,至于要不要彩礼或者要多少彩礼,因人而异,因财力而已,不可一概而论。

用户评论(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

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

点击加载更多内容
上传我的资料

精选资料

热门资料排行换一换

  • 北洋政府职官年表.pdf

  • [中国针灸学].程莘农.扫描版.…

  • Mathematics for …

  • DK.Eyewitness+Bo…

  • 周易江湖.熊逸.陕西师范大学出版…

  • DK[1].Christophe…

  • 英语介词联想记忆 2003.pdf

  • C star -algebras…

  • 北洋军阀军事经济史.pdf

  • 资料评价:

    / 150
    所需积分:0 立即下载

    意见
    反馈

    返回
    顶部