新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 讨论HTML、XHTML、Web2.0、Ajax、XUL, ExtJS, jQuery, JSON、Social Networking System(SNS)、Rich Internet Applications (RIA)、Tagging System、Taxonomy(tagsonomy,folkonomy)、XForms、XFrames、XInclude, XBL (XML Binding Language)等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区XML.ORG.CN讨论区 - XML技术『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』 → 新书《竹林蹊径:深入浅出Windows驱动开发》连载(感谢 电子工业出版社 特许提供) 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 28162 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 新书《竹林蹊径:深入浅出Windows驱动开发》连载(感谢 电子工业出版社 特许提供) 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     admin 帅哥哟,离线,有人找我吗?
      
      
      
      威望:9
      头衔:W3China站长
      等级:计算机硕士学位(管理员)
      文章:5255
      积分:18406
      门派:W3CHINA.ORG
      注册:2003/10/5

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给admin发送一个短消息 把admin加入好友 查看admin的个人资料 搜索admin在『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』的所有贴子 点击这里发送电邮给admin  访问admin的主页 引用回复这个贴子 回复这个贴子 查看admin的博客楼主
    发贴心情 新书《竹林蹊径:深入浅出Windows驱动开发》连载(感谢 电子工业出版社 特许提供)


    竹林蹊径:深入浅出Windows驱动开发
    张佩  马勇  董鉴源 编著
    ISBN 978-7-121-12555-3
    2011年2月出版
    定价:69.00元
    16开
    532页

    内 容 简 介
    本书是作者根据多年的工作学习经验,总结的第一手驱动开发资料。本书更多的是经验之谈,一些实践中的小发现小意外,颇为书中内容添彩。
    本书的特色之一,是对WDF框架做了较多的切入。本书第一个主要内容是(第3~7章)围绕WDF而展开讨论,侧重点各有不同。第3章以框架为讨论的中心;第4、5两章以WDF框架开发USB和1394驱动;第6章讲述内核C++编程,也以WDF框架为蓝本;第7章讲述WDF驱动的测试和调试。
    第二个主要内容是关于音视频驱动开发(第10~11章)。音视频驱动包括AVStream架构,本书做了较详细的阐述。第10章讲述使用AVStream小端口架构,第11章讲述ASIO音频驱动开发。
    第三个主要内容是关于设备驱动安装(第12~14章)。第12章讲系统安装模块,从总体角度阐述系统和设备驱动如何配合完好地进行工作;第13章讲述INF安装文件的细节,包括各个域的作用,以及诸多安装指令的使用。第14章讲如何编写驱动安装软件。
    剩余的一些章节,分别是关于驱动入门(第1、2章)、Windbg调试命令(第8章)、内核同步(第9章)等内容。
    本书适合一般入门级内核程序员,对WDF有兴趣,准备开发USB或1394设备驱动者,本书尤其有用。本书对于入行较久,经验丰富的程序员,也具有一定的参考价值。
    推 荐 序
    我一直认为,编写程序是一件很奇妙的事情,它可以带来创造和控制的欲望。每当我阅读或者编写一段代码时,脑子里自然地就会想象这段代码怎样完成预定的逻辑。当面对一个不熟悉的开发环境,或者一个新的基础平台时,首先要清楚这个环境或者平台是如何工作的,以及提供了哪些功能。代码本身可能非常复杂,甚至奥妙无穷,但通常情况下,真正优美的高质量代码往往是简单的、易于理解的。对于代码编写者或者维护者来说,真正见功夫的地方不在于代码本身,而在于对下层开发平台的理解和驾驭能力,可能这就是俗称的“内功”。
    这个观点既适用于应用软件程序员,也适用于系统软件程序员。对于应用软件程序员,低层的应用开发平台是支撑应用开发的基础,譬如,基于Windows SDK来开发Windows应用程序。那么,程序员有必要理解Windows SDK中的基本要素,诸如消息分发机制、各种图形功能等。在这种情况下,阅读一些典型的例子程序代码往往能起到快速引领入门的效果。同样地,C/C++程序员如果局限于C/C++语言本身,很难编写出高质量的实用程序。他们不仅要掌握C/C++运行库中函数和类型的用法,甚至还要理解这些函数和类型的实现机理。即使源代码层面上的库,例如STL(C++的标准模板库),也需要理解其代码实现才能灵活自如地用好这些库(比如STL中的各种容器数据结构、迭代器或算法)。
    那么,对于系统软件程序员,“内功”是什么呢?系统软件是指操作系统本身或者依附于操作系统上为应用软件提供服务的软件。系统软件可能有机会跟硬件直接打交道,这赋予了程序员更强的控制能力,他们有机会介入操作系统的行为逻辑,甚至改变操作系统的行为特性。但随之而来的是对系统软件代码的更高要求。现代操作系统为应用软件提供了很强的容错能力,应用程序的失败通常不会波及到操作系统自身的稳定性,但操作系统对系统软件的容错能力却比较有限,毕竟系统软件运行起来之后可能被融入到操作系统的执行逻辑中成为操作系统的一部分。因此,理解和掌握操作系统的运行机制成为系统程序员编写出正确、高效的系统软件的基本前提。所谓“内功”,便着落在此。
    在Windows平台上开发软件,编写Windows内核驱动程序是最为考验程序员“内功”的。内核驱动程序的代码量通常不大,但驱动程序框架中的任何一个函数,甚至这些函数中任何一行代码背后都可能蕴含着复杂的逻辑,或者隐式的要求和假设。即使驱动程序编写者在纯粹自行定义的函数中,也必须谨慎地关注一些与环境有关的因素,譬如代码是否可被中断、是否可重入,或者所引用的内存是否被交换到外存。另一方面,应用软件开发中的很多概念,比如地址空间、内存管理、异常处理和多线程并发等,在驱动程序开发中可能需要有不同的理解方法。此外,常用的C运行库函数基本上不再适合于驱动程序了,驱动程序编写者必须面对一个全新的底层环境和支持平台。因此,要编写可正确运行的驱动程序,程序员不仅要清楚地理解驱动程序所针对的目标设备或功能(可能包括硬件设备的各种特性),还要掌握Windows内核是如何与驱动程序打交道的,以及内核中诸多管理和运行机制,尤其是内存管理、线程调度和并发控制。
    当Windows内核驱动程序被加载到内核中并且启动以后,它们变成了Windows内核的一部分,驱动程序中的接口函数在恰当的时刻被内核调用,这是Windows驱动程序的基本工作方式。Microsoft定义了WDM(Windows驱动程序模型)来规定驱动程序的结构,以及Windows内核如何与WDM驱动程序打交道。WDM不仅包括I/O管理器定义的驱动程序框架,还定义了在驱动程序中如何支持PnP(Plug and Play,即插即用)、电源管理和WMI(Windows Management Instrumentation,Windows管理规范)。因此,若要编写一个完全支持WDM的驱动程序,也需要理解WDM中所涉及的各个内核组件。
    Windows内核驱动程序与内核的紧密关联性使得驱动程序的调试极为不方便,从某种意义上讲,驱动程序的调试等同于Windows内核的调试。而且,对于某些特定的逻辑错误,内核调试器甚至是无能为力的。正因为这个原因,内核驱动程序的代码尽可能精简,从软件设计角度而言,应最大程度地把功能代码放到应用程序中,在驱动程序中只留下最必要的功能逻辑。这样的设计也可以使Windows内核被不正确驱动程序代码牵连而导致稳定性问题的几率相对减小。
    为了便于Windows驱动程序的开发,Microsoft定义了一个驱动程序框架,称为WDF(Windows Driver Foundation),其中针对内核驱动程序的部分称为KMDF(Kernel-Mode Driver Framework)。KMDF实际上是一个库,它封装了WDM中一些基本的代码逻辑,从而使程序员可以更加方便地编写出WDM驱动程序。KMDF可以部分地简化Windows内核驱动程序的开发任务,但是本质上它并没有降低内核驱动程序的复杂性,甚至需要程序员付出额外的学习努力。
    总而言之,作为一名系统程序员,你需要洞悉目标操作系统中与你的软件打交道的各个部件,也要非常清楚地知道你所依赖的开发工具是如何帮助你做到这一点的。系统程序员往往面临着比应用程序员更长的学习曲线,但是,系统程序员从编写程序中获得的乐趣也是在应用层上难以体会得到的。我相信,当你发现自己编写的软件模块已经与操作系统内核融为一体时,那一刻你的感觉一定是手心里攥着一个操作系统——操作系统尽在你的掌控中了。
    这本书《竹林蹊径——深入浅出Windows驱动开发》是三位作者张佩、马勇和董鉴源的最新力作,他们将自己在实践中积累起来的经验整理成册,以期望后学者能少走弯路,缩短Windows驱动程序开发的学习之路。这本书重点介绍了KMDF、USB/1394和音频驱动程序的开发,以及设备驱动程序的发行和安装。建议有一定Windows驱动程序开发基础的读者看一看这本书,尤其是,如果你正打算使用KMDF,或者正在从事与USB/1394或音频驱动程序相关的编程工作,那么,这本书便是一份宝贵的实践指导了。
    潘爱民
    2010年12月5日于北京西二旗

    推 荐 序
    我认识一个共享软件的作者,近十年来都在开发他的虚拟光驱的软件,不时给这个软件添加一点新的东西。我对此觉得很奇怪,对他说,我觉得虚拟光驱是一个很简单的东西。用一个映象文件容纳光盘上的数据,并开发一套驱动接口让系统以为这是一个光驱。下载网上开源的代码,应该不超过5000行。为什么他要为此耗费这么多年的精力呢?
    他举了个例子说:国外知名的虚拟光驱Daemon,它的强项在于兼容性。几乎任何软件都能正常使用它的虚拟光驱,并当做真正的光驱来访问。而普通的开源的虚拟光驱,就有很多不支持了。
    他曾经发现一种游戏,要求用户插入光盘。用他自己编写的虚拟光驱来模拟,总是不行。同样的映象文件,换了Daemon就一切OK。这让他大为诧异。碰到这样的情况,他根本就不可能到网上去搜索“为何我的虚拟光驱不支持某某游戏”这样的傻问题,也不可能在某处找来一段代码拷贝一番就解决。他必须找到问题的实质,才能找到对策。
    花去漫长的时间,最终分析的结果是,原来因为该游戏希望每个用户都购买正版光盘,它就用了一种特殊的策略来分析用户所用的是不是真实的光驱。大家都知道硬盘的读取速度一般都比较快,而且事实上也更加稳定。光驱读盘的速度比硬盘相对慢一些,而且读取数据的速度有一定的不稳定性。比如说,数据读取的速率可能会以某种数学模型所定义的曲线为轨迹发生波动。而这个软件就根据这种不稳定性的匹配程度来进行检查。如果虚拟光驱提供的数据是不匹配这种特征的,则它很简单地禁止游戏继续运行。
    而Daemon则在内部插入了这样的模拟函数,有意对数据的读出进行各种延时的处理,使之看上去非常像真实的光驱读出的数据。
    总而言之,他开发的是一个逼近世界顶尖品质的好东西。当然代价是汗水与时间。
    我能想象到在没有任何公开的代码,或者是前人的经验指引,自己去钻研发现并解决这些问题的困难。能在网上找到解决方案的问题必定不会是软件技术里的关键问题。相反是这样一个一个的无头悬案,才构成了程序员们所谓的“核心技术”。
    我和一些人的见解不同。我并不认为越底层的技术就越“核心”。总有人认为系统比软件底层,所以程序更“核心”。而芯片比程序更底层,则芯片又更“核心”。其实硅片比芯片更底层,沙子又比硅片更底层,那是不是沙子才是最核心的技术呢?
    我认为,在任何一个领域里,能够进行持之以恒的钻研,当大部分人选择放弃,而你依旧锲而不舍地学习、研究、解决一个又一个实际问题,你就能掌握核心技术。简言之,善于把握自己能够掌握的知识,并不断深化拓展知识领域,这才是真正的学习之道,也是成就个人和团队核心技术的途径。
    我见到一些工作过多年的人,很有特点。有一种号称对技术没兴趣,更喜欢做管理,但其实并没有那么多做管理的机会,或者真的有机会,而做得也并不理想;有一种全凭在新手面前吹牛皮支撑老资格,实际编程依旧一塌糊涂。我从来不对别人妄加评论,但从技术学习上讲,他们都算是没到家。
    《竹林蹊径——深入浅出Windows驱动开发》终于出版了。据我了解,张佩因写作这本书,在家伏案了半年。他是凭着极大的热情和信念去写作的,否则即便薪资上的损失都很值得惋惜。就本书而言,先不去考量书中内容的深浅,技术的精粗,仅就作者的诚心和写作精神,便值得称赞。
    牛不是一天吃大的,小牛雏要不断地吃进养料,才能变成大牛。牛人要有牛技术,牛的技术,就是核心技术。我希望这本书的每一位读者,不管你现在或是将来,做的是应用开发还是内核编程,不管用的是C++还是Java或.NET,在工作、学习过程中都具有锲而不舍、精益求精的精神,哪怕最菜鸟的新手,在若干年的积累和沉淀后,都能够逐渐形成自己的核心技术。只有掌握了自己的核心技术,才能进入程序员的自由天地。
    此文送给《竹林蹊径——深入浅出Windows驱动开发》的读者,是为序。
    谭文
    2010年12月10日


    前    言
    国内内核开发方面的书籍特别少,一个原因是很多技术牛人,没有时间或机会把自己掌握的知识编辑成书。真的很遗憾。希望我砸出这块砖头后,后面会在书店里看到无数的翡翠之作。
    相比较国外的程序员,国内程序员在学习内核驱动开发的时候,学习曲线特别长,主要原因是没有趁手可用的资料。有一些初学者联系我,倒出的苦水大多是:资料太少,技术太难,不知从何下手。我给出的建议多是希望他们努力学习WDK中的现成文档,并推荐一些经典的英文电子档。但大多数初级学习者,并不满足我这个答案——他们希望有中文资料。这时候,我会向他们推荐《深入解析Windows操作系统》或者《软件调试》,有时候,我还会谨慎地向他们推荐《驱网核心技术丛书》。
    很高兴有机会,写成这样一本书。本书的另外两位作者是马勇和董鉴源,他们分别写了第1章和第2章。写《竹林蹊径——深入浅出Windows驱动开发》花了我整整八个月的时间,有半年左右,我把所有的时间都花在上面,不上班,不娱乐。这本书是我的劳苦之作。
    我利用写作的机会,纵深渗透学习了不少知识。与其说它是在展示个人才华,不如说是做了一次自我进修和测试。我做不到文采飞扬,能保证的是负责任的态度。此书在写作过程中,增删若干遍,完稿之后,请多位前辈老师审稿。我现在唯一期望的是书中的内容,确实能够对读者起到帮助、参考的作用。
    本书特色
    《竹林蹊径——深入浅出Windows驱动开发》这本书的一个最大特点是插图和示例多,对涉及到的大部分知识,能做到一定深度的挖掘。谭文跟我说,无法把自己的技术经验完全写出来。我当然同意他这句话的正确性;但写作的过程中,我仍然尽最大的可能,把技术和经验文字化、图形化,尽量做到由浅入深,脉络分明——这是我个人的最高目标。
    平时给别人讲某个知识点的时候,哪怕是最简单的,我喜欢讲得360度面面俱到。说了一层,爱说下面还有一层。讲了烧水之釜,还要介绍釜底之薪。有人不喜欢这样,但我喜欢。如果以后还写书,我仍会保持这一点。
    本书主要内容
    本书主要包含这几个方面的内容:WDF框架、驱动测试、音视频编程、驱动安装。这其中,最费精神的是WDF框架相关章节。
    WDF是目前和将来驱动开发的大势所趋。如果初学者因为资料的关系,而紧握着WDM架构的双手的话,他一定要留神,不要冷落了旁边正如日中天的WDF。搞技术,特别是在Windows平台上,不建议大伙具有怀旧情绪,跟着形势走是必然的。
    书中有四章内容介绍WDF。笔者如农夫翻地一般,把WDF奇异表面下的具体实现做了一定的揭露。笔者饶有兴趣地为大家分析WDF的对象模型,而从Wdf01000符号文件中揭露的内部结构体定义,能令很多人吃惊不小。
    用WDF框架编写驱动,要比WDM方便、简单一半以上。本书中介绍了使用WDF框架进行USB和1394编程的内容。由于USB的运用之广,使USB驱动成为Windows内核驱动中的显学。驱动开发网专门辟有“USB驱动”版块,并几乎是最聚人气的地方。为配合USB一章的写作与学习,笔者专门请朋友精心设计了一款USB驱动学习开发板。读者在本书中的多处地方,都能看到它的玲珑身影。
    驱动测试方面包括两章内容,一章以WDF驱动测试为中心进行介绍,另一章介绍了Windbg调试命令。曾经的王者SoftIce湮没不闻后,Windbg成了唯一的内核调试利器,不可不掌握。
    音视频驱动向来都比较小众,做相关开发的公司和个人都很少,资料也就更加少。本书有两章内容介绍音视频开发,一章介绍AVStream小端口架构,一章介绍酷酷的ASIO音频驱动,并以虚拟ASIO声卡的创新技术,为有兴趣的读者带去福音。
    本书最后三章,介绍驱动安装有关的知识。一章介绍驱动安装的原理及系统模块,一章介绍INF安装文件的技术细节,一章以示例介绍如何编写驱动安装软件。看过这些内容后,试着为你的驱动写一个安装软件,会很酷。
    本书读者对象
    本书适合一般程序员
    对WDF感兴趣,准备开发VSB或1394设备驱动者
    下面要说一些和技术无关的东西。
    在本书写作过程中,有许多书外的记忆。比如,夏天我工作的时候,我女儿常常站在床头,猛地一下用手扑打我的笔记本,电脑屏幕就倾了下去,一阵惊叫。
    一次两章隔夜刚新鲜写好的内容,保存在移动硬盘中,第二日在另一台电脑上开机,却怎么都找不到了。翻遍整个系统,用了N种数据、磁盘恢复工具,都无济于事,踪影全无。那种无助得想哭的感觉,一直记在心间(这个问题我后来把它再现了,可以认定是Windows 7操作系统的BitLocker功能在休眠唤醒处理上的一个Bug)。
    致谢
    感谢我的好朋友,谭文。是他推荐我主笔这本书的写作。这套系列中的《天书夜读——从汇编语言到Windows内核编程》和《寒江独钓——Windows内核安全编程》出版后,大家都很忙。谭文很信任我,推荐我写《竹林蹊径——深入浅出Windows驱动开发》。谭文是我以前的同事,湖南人,故在网上号楚狂人,赫赫有名,散文随笔一级好,技术文章有散文风。他为本书写了一篇小序,特此感谢。谢谢李冰编辑和文字编辑葛娜女士,她们的信任和支持,使我有可能完成这本书。
    感谢本书两另外两位作者,他们贡献了第1章和第2章。
    感谢潘爱民老师,他为本书写了序,令我有蓬荜生辉之感。
    感谢张银奎老师,张帆兄,他们也对本书给予了鼓励,并写了推荐语。
    我要感谢所有照顾过我的亲人们:谢谢我大阿姨,她现在只在天上看着我们。她以力排众议的气势,关心照顾过我。谢谢红兵表哥,他正好大我一轮,学习成绩冠于全镇。当初我老爱从他那里偷书,那些书正是我童年和少年时代仅有的课外书。谢谢小姨父,父执辈中他是唯一给我严肃、客观教育的人。感谢我舅舅,他给我很多帮助。以前,我总是把去上海说成“去我舅舅那”。
    谢谢我所有的亲人们。
    感谢双方父母,感谢他们所有的辛勤付出。
    最后,感谢我的妻子,近两年以来,她离职在家做专职母亲,好像把十几年的事情放在一两年里做了,岁月催人老,我把她累坏了。
    最后是一首五言八句,会意书名曰:《竹林蹊径》
    荫荫翠竹百亩林,
    结庐恒爱此中景。
    寻常偶遇方外客,
    殷勤指点通幽径。
    张佩  


    目    录
    向内核世界说一声:hello,我来了。如果你是一个初学者,并对这个世界充满好奇心,请从这一章开始,我们一起打招呼~
    第1章  Hello World驱动 1
    1.1  从Hello World开始 2
    1.1.1  HelloDRIVER 4
    1.1.2  代码解释 8
    1.1.3  驱动程序的编译和安装 11
    1.1.4  查看我们的驱动 14
    1.2  虚拟环境 15
    1.2.1  使用虚拟环境进行驱动开发 15
    1.2.2  使用VMware虚拟机 15
    1.2.3  目标机设置 16
    1.2.4  Virtual PC虚拟机 18
    1.3  小结 19
    如何在规范的商业环境中,开发成功而有效的驱动软件?驱网站长马勇(ZnSoft)将向你娓娓道来。你会学到这些内容:建立一个简单而有效的开发、调试环境;64位环境下的内核编程技巧;如何发布你的驱动软件。
    第2章  商业驱动开发技术 20
    2.1  建立开发调试环境 21
    2.1.1  SVN环境 21
    2.1.2  创建工程,导入SVN 23
    2.1.3  建立符号服务器 25
    2.1.4  用符号调试 27
    2.2  64位驱动开发技术 34
    2.2.1  64位驱动编写技术 35
    2.2.2  32位应用程序与64位驱动混合模式 36
    2.3  驱动程序的发布与测试 42
    2.3.1  驱动程序签名 42
    2.3.2  驱动程序测试 46
    2.3.3  WHQL 49
    2.4  小结 50
    WDF是目前最新的驱动编程框架。当很多内核程序员还紧抱WDM的巨大佛脚时,千万要记住,WDF已是大势所趋。本章介绍了WDF最重要的几个概念,并进行了一定程度的深度挖掘。对于WDF框架的三大核心模型:对象模型、事件模型、PNP/Power模型,本章作了重点讲述。
    第3章  WDF概述 51
    3.1  主要特点 52
    3.2  框架视图 53
    3.3  兼容性 55
    3.4  对象模型 56
    3.4.1  对象和句柄 59
    3.4.2  引用计数 60
    3.4.3  上下文空间 61
    3.4.4  PME接口 67
    3.4.5  DDI接口 69
    3.4.6  父子关系 76
    3.4.7  对象同步 77
    3.5  驱动对象和设备对象 78
    3.5.1  驱动对象 78
    3.5.2  驱动入口DriverEntry 81
    3.5.3  设备对象 84
    3.5.4  创建设备对象 85
    3.5.5  设备栈 86
    3.6  IO模型 88
    3.6.1  IO目标对象 88
    3.6.2  IO目标对象的细节 90
    3.6.3  安全的缓冲区 93
    3.6.4  内存对象(一) 96
    3.6.5  内存对象(二) 98
    3.6.6  框架和IO请求 102
    3.6.7  更详细的处理流程 103
    3.6.8  IO请求参数 105
    3.6.9  队列 107
    3.6.10  创建IO请求 110
    3.7  PNP和电源模型 112
    3.8  小结 115
    使用WDF框架开发USB驱动,方便且简单。本章首先总体上从硬件和软件两个方面介绍USB相关知识点,包括设备的电气特性、总线结构、USB驱动类型以及类驱动。编程方面,从USB设备初始化、数据操作以及设备控制等几个方面来讲解,透彻并且翔实。
    第4章  WDF USB设备驱动开发 116
    4.1  USB设备硬件结构 117
    4.1.1  主从结构 117
    4.1.2  硬件拓扑 118
    4.1.3  USB中断 119
    4.2  USB软件结构 120
    4.2.1  总线驱动 120
    4.2.2  系统类驱动 121
    4.2.3  功能驱动 122
    4.2.4  父驱动与混合设备 122
    4.2.5  过滤驱动 125
    4.2.6  USB驱动栈、设备栈 125
    4.3  内核开发 127
    4.3.1  设备驱动 127
    4.3.2  入口函数 128
    4.3.3  USB描述符 129
    4.3.4  描述符介绍 130
    4.3.5  汇总举例 133
    4.3.6  读取描述符 135
    4.3.7  初始化 137
    4.3.8  设备初始化函数 138
    4.3.9  创建设备对象 141
    4.3.10  设备命名、符号链接 143
    4.3.11  启动设备 147
    4.3.12  创建队列 156
    4.3.13  停止设备/反初始化 158
    4.4  数据I/O操作 160
    4.4.1  USB控制命令 160
    4.4.2  构造并发送控制命令 162
    4.4.3  读USB中断端口 163
    4.4.4  连续读操作 165
    4.4.5  数据处理函数 166
    4.4.6  中断端口的效率 167
    4.4.7  读/写批量端口 168
    4.5  设备控制 171
    4.5.1  关于I/O Target对象 171
    4.5.2  获取USB版本 172
    4.5.3  管道重置 174
    4.5.4  设备重置 176
    4.5.5  管道中止与终止 177
    4.6  用户程序 179
    4.6.1  内核读/写 179
    4.6.2  控制命令 179
    4.7  小结 180
    1394俗称火线。大伙平时最多接触它的地方大概是内核调试时,借助1394卡进行双机互联。本章首先从硬件方面介绍了1394的知识,它的总线结构很特别,极具可扩性,能非常方便地在各种类型的1394设备之间建立数据链路。内核编程方面,本章重点讲解了数据通信相关知识,分为异步通信和同步通信两种方式,颇为复杂,相对难于掌握,但套路是现成的,变化的东西不多,可以熟能生巧。本章最后介绍了1394双机互联的原理,有兴趣的读者可参考之。
    第5章  WDF 1394驱动开发 181
    5.1  1394一席谈 182
    5.1.1  版本情况 183
    5.1.2  电源特性 183
    5.1.3  1394卡 183
    5.1.4  总线拓扑 184
    5.2  发送请求 186
    5.2.1  同步方式 187
    5.2.2  异步方式 189
    5.2.3  对WDM的回忆 191
    5.3  总线重置与计数 193
    5.3.1  总线重置 193
    5.3.2  设置重置回调 193
    5.3.3  计数更新 194
    5.4  PNP操作 195
    5.5  异步通信 196
    5.5.1  地址范围 197
    5.5.2  异步读 200
    5.5.3  异步写 201
    5.5.4  异步锁请求 202
    5.5.5  数据流 203
    5.6  等时通信 204
    5.6.1  申请带宽 205
    5.6.2  释放带宽 206
    5.6.3  等时通道 206
    5.6.4  资源句柄 207
    5.6.5  缓冲区挂载 210
    5.6.6  缓冲区解挂 211
    5.6.7  开始传输 211
    5.6.8  停止传输 212
    5.6.9  其他等时操作 213
    5.7  其他操作 213
    5.7.1  设备配置 213
    5.7.2  获取控制器信息 214
    5.7.3  速度信息 215
    5.7.4  厂商自定义命令 216
    5.8  安装与测试 216
    5.8.1  1394虚拟设备 216
    5.8.2  创建虚拟设备 218
    5.8.3  示例代码 219
    5.8.4  安装与测试 221
    5.9  小结 222
    内核天生适合于C语言编程,但越来越多的内核项目,规模达到10数万的规模。在这种情况下,人们不由地会将目光投向优雅的C++语言。总体上说,C和C++是至亲好友,内核中使用C++本不应有什么大问题,但有几个暗礁潜伏已久,不小心的程序员,你可千万不要触礁。
    第6章  内核驱动C++编程 223
    6.1  驱动中的类 224
    6.1.1  一个简单的例子 224
    6.1.2  new/delete 225
    6.1.3  extern "C" 227
    6.1.4  全局/静态变量 228
    6.1.5  栈的忧虑 230
    6.2  类封装的驱动程序 233
    6.2.1  寻找合适的存储所 233
    6.2.2  类方法与事件函数 235
    6.2.3  KMDF驱动实现 236
    6.2.4  WDM驱动实现 237
    6.3  多态 238
    6.3.1  基类、子类 238
    6.3.2  实现多态 239
    6.3.3  测试 241
    6.4  小结 241
    使用WDF框架编写的驱动程序,在测试和调试的时候,有特殊的工具。本章介绍了目前所知的三个,它们分别是:Windbg扩展调试命令、WDFTester测试工具、WDFVerifier测试工具。本章将以示例方式,介绍这些工具的使用。
    第7章  WDF驱动测试 242
    7.1  WDF错误 243
    7.1.1  实例分析 245
    7.1.2  USB错误 246
    7.2  WDF扩展调试命令 247
    7.3  WDFTester 254
    7.3.1  WDFFiTester 254
    7.3.2  使用 256
    7.3.3  WDFCallTracer 260
    7.4  WDFVerifier 263
    7.4.1  识别KMDF驱动 263
    7.4.2  使用与介绍 265
    7.5  小结 266
    SoftIce渐行渐远之后,Windbg成为内核调试的第一利器。使用Windbg的最大难点是命令繁多,参数复杂。本章以总结归纳的形式,介绍了作者在工作中经常用到的几大类调试命令,并以实例形式一一介绍。作者根据个人经验所作的分类,未能全备,但能够保证的是,所有实例翔实而可靠,可以作为可信的参考。
    第8章  调试命令详解 267
    8.1  概述 268
    8.1.1  寻求帮助 269
    8.1.2  DML语言 270
    8.1.3  基本信息 271
    8.1.4  基本设置 272
    8.1.5  格式化显示 273
    8.1.6  开始调试 273
    8.2  符号与源码 276
    8.2.1  模块列表 277
    8.2.2  模块信息 279
    8.2.3  符号路径 280
    8.2.4  符号加载 283
    8.2.5  符号搜索 285
    8.2.6  源码命令 287
    8.3  进程与线程 289
    8.3.1  进程命令 289
    8.3.2  线程命令 292
    8.3.3  异常与事件 296
    8.3.4  局部变量 300
    8.3.5  显示类型 301
    8.4  断点 301
    8.4.1  软件断点 301
    8.4.2  硬件断点 303
    8.4.3  其他操作 303
    8.5  内存命令 304
    8.5.1  查看内存 304
    8.5.2  内存信息 307
    8.5.3  其他命令 311
    8.6  小结 312
    相信大多数人在学习内核开发的时候,都问过这样一个问题:内核驱动怎么向用户程序发送消息,或者如何调用Win32函数。用户程序和内核同步,是一个基本而重要的知识,本章介绍了三种主要的实现方式。至于内核是否可以调用Win32函数,读一读本章开篇的话,你就有答案了。
    第9章  内核同步 313
    9.1  关于内核同步 314
    9.2  内核事件同步 316
    9.2.1  原理 316
    9.2.2  用户程序 318
    9.2.3  内核实现 319
    9.3  IRP同步 320
    9.3.1  用户程序 321
    9.3.2  内核实现 323
    9.4  WMI同步 325
    9.5  数据缓冲区同步 326
    9.6  反向调用 328
    9.7  小结 330
    微软最新的音视频编程框架即AVStream框架,不管从什么方面来说,音视频编程都是一个很小众的领域。AVStream框架极其复杂,个人看法是掌握的难度超过了WDF。本章介绍了AVStream框架的各种基本知识点,并以实例讲解一个内核音频过滤器在系统中是如何工作的。
    第10章  音频驱动开发 331
    10.1  简介 332
    10.1.1  音频模块架构 332
    10.1.2  系统中的音频设备 334
    10.2  AVStream对象 338
    10.2.1  设备对象 339
    10.2.2  Filter工厂和Filter对象 340
    10.2.3  Pin工厂和Pin对象 342
    10.2.4  Node对象与Connection结构体 343
    10.3  AVStream描述符 346
    10.3.1  描述符简介 346
    10.3.2  描述符示例 347
    10.3.3  分发函数表 349
    10.3.4  自控表 349
    10.3.5  自控表示例 351
    10.4  代码讲解 355
    10.4.1  入口函数 355
    10.4.2  设备分发函数 357
    10.4.3  Filter与Pin分发函数 358
    10.4.4  创建和删除 359
    10.4.5  数据处理 360
    10.4.6  数据格式 362
    10.5  自控表函数 364
    10.5.1  事件函数 364
    10.5.2  属性函数 366
    10.5.3  方法函数 367
    10.5.4  用户接口 367
    10.6  硬件操作 370
    10.6.1  数据DMA 370
    10.6.2  AVStream中的DMA实现 371
    10.6.3  谈谈ISR 374
    10.7  安装与测试 376
    10.7.1  安装 376
    10.7.2  测试工具 376
    10.8  小结 379
    ASIO音频驱动具有两个非常亮眼的优点:低延迟、多通道。低延迟能够达到几毫秒,使得最灵敏的耳朵也难也察觉;多通道则让通常的双声道、6.1声道等一齐歇菜,而可以很轻松地让多达十几、几十个声道同时工作,在进行高级音频编辑时,这非常重要。
    第11章  ASIO虚拟声卡 380
    11.1  引言 381
    11.2  关于ASIO 383
    11.3  ASIO用户驱动 384
    11.3.1  COM接口 384
    11.3.2  安装与卸载 386
    11.3.3  IASIO接口 387
    11.3.4  技术核心 390
    11.3.5  计算延迟 392
    11.4  内核驱动实现 393
    11.4.1  同步 393
    11.4.2  原理 393
    11.4.3  实现 396
    11.5  ASIO音频软件 396
    11.6  小结 397
    从本章开始的三章内容,讲的都是“驱动安装”这个话题。在本章中,介绍了系统中和驱动安装有关的各种系统模块。读者通过阅读本章后,至少能够掌握这两个基本知识:系统如何识别一个旧设备,并为它加载合适的驱动文件;系统如何发现一个新设备,并完成驱动安装。
    第12章  设备驱动安装入门 399
    12.1  基础知识预介 400
    12.1.1  设备类型 400
    12.1.2  设备实例ID 401
    12.1.3  驱动加载和安装 403
    12.2  安装模块 404
    12.2.1  内核PNP管理器 405
    12.2.2  用户PNP管理器 406
    12.2.3  安装接口函数(Setup API) 408
    12.2.4  配置管理器接口(CfgMgr API) 410
    12.2.5  类安装器(Class Installers) 410
    12.2.6  类协安装器(Class Co-Installers) 410
    12.2.7  设备协安装器(Device Co-Installers) 411
    12.2.8  驱动包(Driver Package) 412
    12.2.9  驱动仓库(Driver Store) 413
    12.2.10  设备管理器(Device Manager) 414
    12.2.11  安装程序 415
    12.2.12  新设备向导 416
    12.2.13  添加硬件向导 416
    12.2.14  驱动安装器(Driver Installer) 416
    12.3  重要问题 417
    12.3.1  寻找和选择 417
    12.3.2  32位与64位系统兼容 418
    12.3.3  系统重启 419
    12.4  安装模式 420
    12.4.1  示例1:客户端模式 421
    12.4.2  示例2:服务器模式 423
    12.5  安装器编程 424
    12.5.1  DIF码 424
    12.5.2  处理流程 427
    12.5.3  工程示例 429
    12.5.4  注册 430
    12.6  小结 431
    INF文件即驱动程序的“安装文件”,它包含了各种与驱动安装有关的指令信息。通过INF文件,系统知道如何处理驱动包中的各个文件,并在系统注册表中做出准确记录。本章主要从指令和域,这两个方面进行讲解。
    第13章  深入解析INF文件 432
    13.1  概述 433
    13.1.1  域 433
    13.1.2  指令 434
    13.1.3  多系统 435
    13.2  注册表指令 436
    13.2.1  缩写根键 436
    13.2.2  软件键 437
    13.2.3  硬件键 437
    13.2.4  AddReg 438
    13.2.5  DelReg 440
    13.2.6  BitReg 441
    13.3  文件操作指令 441
    13.3.1  CopyFiles 441
    13.3.2  DelFiles 443
    13.3.3  RenFiles 443
    13.4  服务指令 444
    13.4.1  AddService 444
    13.4.2  DelService 445
    13.5  基本域 446
    13.5.1  版本域 446
    13.5.2  文件域 447
    13.5.3  默认安装域 451
    13.5.4  控制域 454
    13.5.5  字符串域 457
    13.6  设备类安装域 458
    13.6.1  主域 459
    13.6.2  服务子域 461
    13.7  接口类安装域 461
    13.8  厂商/产品域 462
    13.8.1  厂商域 463
    13.8.2  产品域 464
    13.9  设备安装域 464
    13.9.1  硬件子域 466
    13.9.2  协安装器子域 467
    13.9.3  接口子域 468
    13.9.4  厂商默认配置子域 469
    13.9.5  逻辑优先配置子域 470
    13.10  ChkInf介绍 471
    13.11  小结 472
    驱动安装程序让你的驱动软件显得更加专业,所以,放弃手动安装驱动的做法吧,你的驱动将显得更靓。本章的示例软件MyDrvInst,可以作为读者设计更漂亮的安装软件的开始。
    第14章  设计驱动安装程序 473
    14.1  驱动包 474
    14.1.1  安装方式 474
    14.1.2  安装驱动包 475
    14.1.3  卸载驱动包 476
    14.2  驱动更新 477
    14.2.1  设备已连接 477
    14.2.2  设备未连接 478
    14.2.3  枚举系统设备 481
    14.3  分析INF文件 484
    14.3.1  函数介绍 484
    14.3.2  打印设备ID 486
    14.4  MyDrvInst介绍 487
    14.5  制作软件安装包 490
    14.5.1  视图介绍 490
    14.5.2  我们的工程 492
    14.5.3  编译执行 493
    14.6  小结 494
    附录A  CY001 USB开发板 495
    附录B  VisualKD + VMWare实现单机内核调试 501


       收藏   分享  
    顶(0)
      




    ----------------------------------------------

    -----------------------------------------------

    第十二章第一节《用ROR创建面向资源的服务》
    第十二章第二节《用Restlet创建面向资源的服务》
    第三章《REST式服务有什么不同》
    InfoQ SOA首席编辑胡键评《RESTful Web Services中文版》
    [InfoQ文章]解答有关REST的十点疑惑

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2011/2/15 11:20:00
     
     admin 帅哥哟,离线,有人找我吗?
      
      
      
      威望:9
      头衔:W3China站长
      等级:计算机硕士学位(管理员)
      文章:5255
      积分:18406
      门派:W3CHINA.ORG
      注册:2003/10/5

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给admin发送一个短消息 把admin加入好友 查看admin的个人资料 搜索admin在『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』的所有贴子 点击这里发送电邮给admin  访问admin的主页 引用回复这个贴子 回复这个贴子 查看admin的博客2
    发贴心情 

    第6章  内核驱动C++编程

    6.1  驱动中的类
    很少有专题讲内核中的C++编程,中文资料恐怕更是罕见。由于C++的普及性、与C的亲密关系,以及大部分情况下程序员都使用C++编译器编译C程序的事实,当初学者听说内核中“不容易”(笔者也听说过“无法”二字)用C++进行编程时,会大吃一惊。不管是说者无意,还是听者有心,Windows内核的现状,决定了C语言是内核编程的首选。
    其实内核驱动中也能使用C++,也能使用类,但和用户程序中的用法有一些区别,一些特殊的地方需要特别注意。从笔者的经验来看,WDK给出的AVStream小端口驱动示例工程,就都是C++代码,这是由于AVStream的模块性非常强,在实现较大功能模块时,非得用类封装,否则难以表述清楚。
    本章专门讲述如何在内核中编写C++驱动程序。笔者先写一个简单的例子,显示类的一些基本特性,并由此交代出几项关键点;然后改造《WDF USB设备驱动开发》一章中的WDFCY001驱动的例子,将它全部改造成一个驱动类,并最终实现C++的最大优点:多态。
    6.1.1  一个简单的例子
    首先我们尝试把用户程序中最简单的类拷贝到内核中,编译链接,看看行不行。下面就是笔者定义的整数类,它封装一个整数,对象能够被当成整数使用。
    class  clsInt{
    Public:   
       clsInt(){m_nValue = 0;}
       clsInt(int nValue){m_nValue = nValue;}
       void  print(){KdPrint((“m_nValue:%d\n”, m_nValue));}
       operator int(){return m_nValue;}

    private:
       int  m_nValue;
    };
    上例是一个非常简单的类定义,我们将在DriverEntry函数中使用它,分别定义一个局部变量和动态创建一个对象。我们通过Debug信息来观察对象行踪,希望能够得到正确的输出。入口函数中的定义如下:
    extern "C" NTSTATUS DriverEntry(
         IN PDRIVER_OBJECT  DriverObject,
         IN PUNICODE_STRING  RegistryPath
         )
    {
     // 创建两个对象,一个是局部变量,一个是动态创建的
     clsInt obj1(1);
     clsInt* obj2 = new(NonPagedPool, 'abcd') clsInt(2);

     // 打印Log信息
     obj1.print();
     obj2->print();
     delete obj2;

     // 让模块加载失败
     return STATUS_UNSUCCESSFUL;
    }
    上面代码中先后创建了两个clsInt对象,一个是在栈中创建的,初始变量为1;一个是动态创建的,初始变量为2。后者由于是动态创建的,必须手动调用delete函数释放内存,所以其析构函数比前者先调用。我们必须从Log信息中得到类似的脉络,以证明其正确性。代码请参看simClass工程。图6-1是Log信息的截图,我们如愿以偿地得到了想要的结果。

    图6-1  对象Log信息
    6.1.2  new/delete
    查看上面的代码,会发现一个不同于以往的new操作符。这是怎么回事呢?我们这一节就讲讲它。在用户程序中,创建和释放一个对象使用 new/delete方法,其底层乃是调用HeapAllocate/HeapFree 堆API从线程堆栈中申请空间。但问题是,内核CRT没有提供new/delete操作符,所以需要自己定义。自定义的new/delete操作符,自然也是能够从堆栈中分配内存的,内核中有RtlAllocateHeap/RtlFreeHeap堆栈服务函数。但在内核中,我们一般使用内存池来获取内存,实际上内存池和堆栈使用了同一套实现机制。使用ExAllocatePool/ExFreePool函数对从内存池申请/释放内存,下面是一个例子。
    __forceinline
    void* __cdecl operator new(size_t size,
                                    POOL_TYPE pool_type,
                                    ULONG pool_tag)
    {
      ASSERT((pool_type < MaxPoolType) && (0 != size));
      if(size == 0)return NULL;

      // 中断级检查。分发级别和以上的级别只能分配非分页内存
      ASSERT(pool_type ==  NonPagedPool ||
        (KeGetCurrentIrql() < DISPATCH_LEVEL));
      
      return ExAllocatePoolWithTag(pool_type,
                                  static_cast<ULONG>(size),
                                  pool_tag);
    }
    上面的函数定义有几个细节的地方应当注意一下。首先注意new操作符重载,它的第一个参数一定是size_t,用来表示将分配缓冲区的长度;其次注意分页内存和非分页内存的区别,即pool_type所表示者,在DISPATCH_LEVEL及以上的级别是不能分配分页内存的。
    下面是使用new进行内存申请的一个例子。
    // 定义一个32位的TAG值
    #define  TAG  'abcd'
    // 外部已经定义了一个clsName类
    extern  class  clsName;

    // 为clsName申请对象空间
    clsName*  objName = NULL;
    objName = new(NonPagedPool, TAG)clsName();
    上面的new操作和用户程序中的new操作具有同样的功效,但需要注意第一个参数size_t是必须外置的,编译器会自动用sizeof(clsName)求取长度并作为第一个参数。一般地说,对于类似下面的语句:
    className  objName = new(…)  className(…)
    其执行过程是,首先由new操作符为新对象动态分配内存,并返回指针;然后再对此新创建的对象,选择与className(…) 相符的构造函数进行初始化。
    再来看看delete操作符的重载。
    __forceinline
    void __cdecl operator delete(void* pointer)
    {
      ASSERT(NULL != pointer);
      if (NULL != pointer)
        ExFreePool(pointer);
    }
    删除对象数组,即delete[]操作符重载。
    __forceinline
    void __cdecl operator delete[](void* pointer)
    {
      ASSERT(NULL != pointer);
      if (NULL != pointer)
        ExFreePool(pointer);
    }
    上面两个函数最终都会将指定地址的内存释放,但在释放之前,前者会调用指定对象的析构函数,后者会对数组中每个成员调用析构函数。示例如下:
    extern  clsName  *objName;
    extern  clsName  *objArray[];
    delete  objName;
    delete[]  objArray;
    6.1.3  extern "C"
    对extern "C"编译指令,大家不会感到陌生。它一般这样用:
    extern "C"{
    //…内容
    }
    既然是编译指令,就一定是作用于编译时刻的。它告诉编译器,对于作用范围内的代码,以C编译器方式编译。一般是针对C++/Java等程序而用的。如果括号内仅有一项,那么括号可以省略。
    最早让我们见识到它的作用的是在入口函数DriverEntry中。现在必须这样声明它:
    extern "C" NTSTATUS DriverEntry(
           IN PDRIVER_OBJECT  DriverObject,
           IN PUNICODE_STRING  RegistryPath
           );
    初学者未必知道这一点,如果“忘记”做上述改动,将得到如下错误:
    error LNK2019: unresolved external symbol _DriverEntry@8
    referenced in function _GsDriverEntry@8
    error LNK1120: 1 unresolved externals
    很奇怪,这是一个链接错误,说明编译过程是通过的。怎么回事呢?认真看一下错误内容,原来是系统在链接时找不到入口函数_DriverEntry@8。这个奇怪的函数名,很显然是C编译器对DriverEntry进行编译后的结果,前缀“_”是C编译器特有的,后缀“@8”是所有参数的长度。原来我们现在使用的是C++编译器,一定是它把DriverEntry编译成了系统无法认识的另一副模样了(实际上,C++编译器会把它编译成以“?DriverEntry@@”开头的一串很长的符号)。
    一旦加上extern "C"修饰符,上述问题即立刻消失了。extern "C"提醒编译器要使用C编译格式编译DriverEntry函数,这样编译生成的函数名称为“_DriverEntry@8”,链接器即可正确地识别出符号了。
    6.1.4  全局/静态变量
    首先列出规则如下:
    不能定义类的全局或者静态对象,除非这个类没有构造函数;否则全局对象将因初始化过程中含有无法解决的符号,而导致链接失败。
    读者可能难以理解这个规定,所以要用实例进行更深的挖掘才行。以simClass的clsInt类为例,如果定义如下全局变量:
    clsInt  gA;
    对项目进行编译,会毫不留情地得到如下错误(也是链接错误):
    errors in directory c:\trunk\simclass
    c:\trunk\simclass\main.obj : error LNK2019: unresolved external symbol _atexit referenced in function "void __cdecl 'dynamic initializer for 'gA''(void)" (??__EgA@@YAXXZ)
    上面的链接错误,是由于函数??__EgA@@YAXXZ中找不到符号_atexit。这两个名字都怪得不得了!理解它们要从C++标准说起,C++标准规定对于全局对象的处理,编译器要保证全局对象在main()函数运行之前已经被初始化,并且保证main()函数在退出前被删除(析构)。变量的初始化与删除,需要编译器专门为它们各自创建一个函数,并在合适的时机进行调用。函数名称根据不同的编译器会有所不同,在这里看到,用于对gA进行初始化的是函数??__EgA@@YAXXZ,笔者通过IAD反汇编后看到,用于删除(析构)的是函数??__FgA@@YAXXZ。后者一点问题都没有,但前者遇到了问题,无法解析_atexit符号。笔者将其汇编代码拷贝如下:
    // 函数名,注释很明白地告诉我们,此函数是gA的初始化函数
    ??__EgA@@YAXXZ:      ; DATA XREF: .CRT$XCU:_gA$initializer$o
    0000031E   mov     edi, edi
    00000320   push    ebp
    00000321   mov     ebp, esp

    // 下面首先会调用clsInt的默认构造函数
    // 第一句是将m_nValue赋值为0
    00000323   mov     ds:clsInt gA, 0

    // 下面是DbgPrint调用
    0000032D   mov     eax, ds:clsInt gA
    00000332   push    eax
    00000333   push    offset clsInt gA
    00000338   push    offset PrintString
    0000033D   call    _DbgPrint
    0000033D
    00000342   add     esp, 0Ch

    // 初始化已经完毕了,问题出在这里
    //初始化完毕后,把??__FgA@@YAXXZ地址作为参数,调用_atexit以注册终止函数
    00000345   push    offset ??__FgA@@YAXXZ
    0000034A   call    _atexit
    0000034A

    // 恢复堆栈
    0000034F   add     esp, 4
    00000352   pop     ebp
    00000353   retn
    00000353
    00000353 _text$yc        ends
    上面的汇编代码,大部分都是正确的,只是到了最后调用_atexit函数时才出了错(_atexit是导入符号,实际函数名应去掉前面的“_”,即atexit)。atexit是一个C标准函数,其作用是向系统注册终止函数,即主程序在终止之前需调用的处理函数。上面我们看到,atexit将??__FgA@@YAXXZ作为参数进行了调用以析构gA。在逻辑上是没有问题的,但atexit函数在内核中未实现。实际上,它有下面的一行调用:
    atexit(??__FgA@@YAXXZ);
    现在的问题就归结为:内核中没有C运行时函数atexit。请问:它可以有吗?它难道不可以有吗?
    上面笔者也说过,内核代码和用户程序是非常不一样的。用户程序的生命周期由main()调用开始,main()调用结束,整个程序也即完结。而驱动程序却不一样,虽然我们有时候把DriverEntry比作main(),但二者在本质上不同,DriverEntry的生命周期非常短,其作用仅是将内核文件镜像加载到系统中时进行驱动初始化,调用结束后驱动程序的其他部分依旧存在,并不随它而终止。所以我们一般可把DriverEntry称为“入口函数”,而不可称为“主函数”。因此作为内核驱动来说,它没有一个明确的退出点,这应该是atexit无法在内核中实现的原因吧。
    从图6-2我们看到,用户程序是一个独立运行单位,main()函数是主线程,它的生命周期也就是程序的生命周期。而内核驱动呢?它的生命周期其实只是镜像文件的生命周期,即加载与卸载,并没有固定的主线程与之匹配甚至支配其生命周期;相反,驱动代码可以出现在任何线程环境中,被任何线程调用。
    话说回来,其实驱动程序也是有明显的生命周期的,即从DriverEntry开始到DriverUnload结束的镜像文件的生命周期,如图6-3所示。这也并非不可利用,笔者觉得,如果在DriverEntry调用前执行全局对象的初始化函数,而同时把终止函数注册到DriverUnload中,或许能够解决问题,但前提是要求系统要做相应的改动了。因为DriverUnload是可选的,所以若采用这种方法,应采取措施为未提供DriverUnload函数的驱动设置默认的卸载函数。但随着微软对这方面研究的深入,笔者相信,这个问题一定是他们的问题列表中必须解决的一项。

    图6-2  用户程序

    图6-3  内核假想实现
    本节内容代码,请参看本书simClass示例工程。
    内核中使用C++还有一点需要注意,就是C++编译器会在不提醒的情况下,使用堆栈生成临时变量若干,而内核堆栈是非常有限的,所以常常需要对此保持一份警惕。
    6.1.5  栈的忧虑
    普通的Win32线程有两个栈:一个是用户栈,另一个是内核栈;而如果是内核中创建的系统工作线程,则只有内核栈。只要代码在内核中运行,线程就一定是使用其内核栈的。栈的主要作用是维护函数调用帧,以及为局部变量提供空间。
    用户栈可以指定其大小,默认是1MB,通过编译指令/stack可改设其他值。
    普通内核栈的大小是固定的,由系统根据CPU架构而定,x86系统上为12KB,x64系统上为24KB,安腾系统上为32KB。对于GUI线程,普通内核栈空间可能不够,所以系统又定义了“大内核栈”概念,可以在需要的时候增长栈空间。只有GUI线程才能使用大内核栈,这也是系统规定的。
    关于GUI线程,笔者多说几句。Windows的发明,将GDI和USER模块,即“窗口与图形模块”的实现移到了内核中,称为Windows子系统内核服务,并形成一个win32k.sys内核文件。而用户层仅留调用接口,由User32.dll和GDI32.dll两个文件暴露出来。判断一个线程是不是GUI线程的依据,竟非常的简单:线程初建时,都是普通线程,第一次调用Windows子系统内核服务(只要用户程序调用了User32.dll和GDI32.dll中的函数,并导致相关内核服务在内核中被执行),系统即立刻将之转变为GUI线程,并从而切换到“大内核栈”;倘若至线程结束,并未有任何一个子系统内核服务被调用,那么它一直都是普通线程,一直使用普通内核栈。
    正是由于窗口与图形模块的内移,才导致了相关服务必须在内核中执行,从而不得不引入“大内核栈”概念。笔者知道UNIX系列的操作系统,包括Linux、Mac,都是在用户层实现窗口与图形子系统的,这类操作系统甚至可以毫不影响地在多个图形子系统间进行切换。回忆Windows NT 4以前的操作系统,其设计也和UNIX一样,相关模块放在用户层实现。在这种情况下,对于上述操作系统,按照笔者的理解,它们就没必要使用大内核栈的概念了——笔者仔细查过Linux和UNIX相关书籍,确实未找到“大内核栈”的说明。
    和C编译器相比,C++编译器更善于为目标代码做较多优化,并因为创建数量不等的临时变量而占用一定的栈空间。对于用户栈和大内核栈,临时变量带来的栈空间支出一般不足以构成问题。但对于普通内核栈,C++编译器并不知道自己正在多么奢侈地挥霍着有限而珍贵的资源,几十K甚至十几K的内存很容易被耗尽,内核栈溢出因此成为一个非常大的威胁。
    下面给读者举一个语言上的例子。对于表达式:
    A = b + c
    如果a、b、c三个变量的类型为:
    int a, b, c;
    虽然不同的编译器间各有不同的实现,但一般来说编译后的结果是这样的:先把b的值存入一个寄存器中(如eax),将寄存器和c相加,再把寄存器值传入变量a。这里面不涉及临时变量。
    但如果a、b、c三个变量的类型为一个类,如ClsSome:
    ClsSome a, b, c;
    则编译后的结果就不像表面上那么简单了,编译器会创建一个ClsSome类型的临时变量tmp,并将b与c相加后的结果存入tmp中,最后用赋值操作将临时对象tmp赋值给a。临时变量tmp是编译器神不知鬼不觉创建的,程序员很难预知这一背后动作。
    对于上面的对象例子,如果有更多的对象参与并实现了更复杂的操作,则编译器创建的临时变量数将更多,可能超乎你的想象。Lippman在其《Inside The C++ Object Model》一书中举了一例,是三个对象之间的四则运算:
    a = b + c - b*c; // 见其书6.3节,原是a[i] = b[i]+c[i] – b[i]*c[i],是一个对象数组
    Lippman举此例后,称这里面将会导致创建5个临时变量,岂不令人惊讶!
    对于因为内核栈空间的瓶颈而引起的忧虑,目前并没有好的解决方法。可能读者会疑惑一个问题,即为什么不能把内核栈也设计得和用户栈一样呢?比如把内核栈默认大小设置为1MB,用户栈这么做并没有带来任何问题啊。
    提出这个问题的读者很会动脑子,但他忽略了一个问题,就是用户空间和内核空间的不同之处。用户空间是进程独立的,以x86系统为例,在正常情况下,每个进程都有独立的2GB用户空间,所以用户栈的1MB并不起眼。
    而内核空间是全局共享的,所有内核栈都在同一个内核空间中申请内存资源。如果内核栈也像用户栈一样,将大小设到1MB,我们来算一笔账吧。系统中的线程成百上千,就算平均500个线程吧,每个线程一个1MB大小的内核栈,一共占了500MB。这还了得吗?岌岌可危。500个线程太保守了,笔者在写作的当下系统中有967个线程(见图6-4),那就用掉将近1半的内核空间了!再倘若用户开启了/3G开关,那么内核空间就只有1GB——系统要喊救命了,可了不得!
    在任务管理器中,在选择列对话框中勾上“线程数”,能看到各进程含有的线程数,将所得数相加能得到一个大概的系统线程总数;但更好的办法是查看系统的性能计数,可使用perfmon来查看,如图6-4所示。

    图6-4  查看系统线程数
    内核栈的问题,正是内核中使用C++的一个最大障碍。在实际编程时,为了尽量避免发生栈溢出错误,需要经常对栈剩余空间保持一份警惕,尤其在可能形成很深的调用栈(如递归调用)的情况下。内核函数IoGetStackLimits与IoGetRemainingStackSize分别用来获取当前内核栈的边界与剩余空间,可使用这两个函数实时控制栈状况。可在函数入口处包含下列代码。
    // 如果当前内核栈空间小于150字节,就让函数返回
    if(IoGetRemainingStackSize() < 150)
        return;      // 如有可能,可指定一个特殊的错误值
    6.2  类封装的驱动程序
    上面的clsInt太过简单了,无法回答这样的问题:在内核中使用类能带来什么好处?simClass工程无法回答上述问题,笔者只是借助它引出并解决一些基本问题。下面我们思考这样一个问题:就驱动本身而言,如何把内核驱动封装成一个类?
    内核驱动,无外乎就是一些数据结构:驱动对象、设备对象、文件对象、IRP等;而对这些数据结构的处理就是内核函数:WDM驱动乃是分发函数(Dispatch Function),WDF乃是事件(Event)。
    这不正好吗?上述二者恰好是类封装的基本要素!类者,数据加方法。笔者将把诸如驱动对象、设备对象等一切用到的数据结构,作为成员数据;把分发函数或者事件、回调,作为成员函数。一个“驱动类”就此初露峥嵘了。
    想法是不错的,但遇到两个问题,下面一一说明。
    6.2.1  寻找合适的存储所
    定义类之前要解决的第一个问题是,一旦类对象被创建后,它的生命周期基本上要和驱动程序的生命周期相当,在哪里保存类对象呢?创建全局变量当然是一种方法,但存在多个驱动实例时就会发生冲突。在WDM驱动中,有设备扩展可以保存自己的变量。KMDF则更丰富,笔者最终决定在WDFDRIVER对象中保存类对象。达成的效果如图6-5所示。
    驱动对象和设备对象是驱动程序的核心,而回调函数又是核心的核心。在图6-5中,驱动对象和设备对象的回调函数,都在DrvClass类中实现。而为了让C++类对象的生命周期和驱动对象保持一致,用一个WDMMEMORY对象将它封装起来,并作为驱动对象的子对象,由框架自动维护,在驱动对象存在时,C++类对象将一直是有效的。
    首先看看怎么把一个自定义的内容保存到驱动对象中,这又要用到框架对象的“环境变量”概念了,前面我们学过给设备对象设置环境变量,现在轮到驱动对象了。让我们重新来做一遍。

    图6-5  对象模块图
    第1步,定义一个获取环境块指针的函数。
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DRIVER_CONTEXT,
        GetDriverContext);    
    上面的宏将定义一个名称为GetDriverContext的函数,这个函数的伪代码如下:
    *DRIVER_CONTEXT GetDriverContext(WDFOBJECT Object)
    {
     // XXX是一个固定的地址,由于未文档化,无法知道其具体定义
     return (DRIVER_CONTEXT*)Object->XXX;
    }
    以后只需要进行如下调用,即能取得驱动对象的环境块指针(前提是传入正确的对象句柄)。
    // 获取环境变量
    DRIVER_CONTEXT  *pContext = GetDriverContext(WdfDriver);
    第2步,在WdfDriverCreate创建框架驱动对象的同时,设置环境变量的结构,通过WDF_DRIVER_CONFIG完成。下面代码的前面部分,实现了此步。
    第3步,调用GetDriverContext获取环境变量,并将其封装到一个WDFMEMORY对象中,并指定第2步中创建的驱动对象为其父对象,以令框架自动维护其生命周期。下面代码的后面部分,实现了此步。
    NTSTATUS DrvClass::DriverEntry(
         IN PDRIVER_OBJECT DriverObject,
         IN PUNICODE_STRING RegistryPath)
    {
     KDBG(DPFLTR_INFO_LEVEL, "DrvClass::DriverEntry");

     WDFMEMORY hDriver;
     WDF_OBJECT_ATTRIBUTES attributes;
     WDF_DRIVER_CONFIG config;
     NTSTATUS status = STATUS_SUCCESS;
     WDFDRIVER WdfDriver;

     // 设定驱动环境块长度
     // 宏内部会调用sizeof(…)求结构体长度,并用粘连符(##)获得其名称
     WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DRIVER_CONTEXT);

     WDF_DRIVER_CONFIG_INIT(&config, DrvClass::PnpAdd_sta);
     status = WdfDriverCreate(DriverObject,  // WDF驱动对象
      RegistryPath,
      &attributes,
      &config,        // 配置参数
      &WdfDriver);

     // 取得驱动环境块
     PDRIVEDR_CONTEXT pContext = GetDriverContext(WdfDriver);
     ASSERT(pContext);
     pContext->par1 = (PVOID)this;
          
     // 把类对象用WDFMEMORY对象封装后,作为WDFDRIVER对象的子对象
     WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
     attributes.ParentObject = WdfDriver;
     attributes.EvtDestroyCallback  = DrvClassDestroy;
     WdfMemoryCreatePreallocated(&attributes, (PVOID)this,
            sizeof(DrvClass), &hDriver);

     KDBG(DPFLTR_INFO_LEVEL, "this = %p", this);
     return status;
    }
    驱动程序将在入口函数DriverEntry中动态创建一个类对象,并即刻调用方法DrvClass::DriverEntry,以创建驱动对象并将其作为对象的存储所。
    以这种方法实现的妙处是,对象的维护是自动化的,我们不用操心太切。一切看上去,很是完美。下面是DrvClassDestroy函数的实现,WDF框架会在销毁内存对象时自动调用它,我们在其中销毁类对象。
    VOID DrvClassDestroy(IN WDFOBJECT  Object)
    {
     PVOID pBuf = WdfMemoryGetBuffer((WDFMEMORY)Object, NULL);
     delete pBuf;
    }
    6.2.2  类方法与事件函数
    KMDF中的事件函数,分开来说:驱动对象有EvtDriverDeviceAdd和EvtDriverUnload,我们将实现前者;设备对象有一系列PNP/Power事件;还有其他对象的事件函数,且忽略之,详见代码。
    事件函数说到底是一种回调函数。类普通成员函数,由于编译后会增加this参数,所以无法成为回调函数。只能使用类静态函数,并通过静态函数再回调成员函数。这是一种很通用的实现手段。以EvtDriverDeviceAdd事件函数为例,我们要在类中为它定义两个相关函数。
    Class DrvClass
    {
     // 定义类静态函数,它是全局的,可以作为回调函数
     static NTSTATUS PnpAdd_sta(
         IN WDFDRIVER  Driver,
         IN PWDFDEVICE_INIT  DeviceInit);
     
     // 再定义类成员函数,将由静态函数内部调用
     virtual NTSTATUS PnpAdd(
         IN WDFDRIVER  Driver,
         IN PWDFDEVICE_INIT  DeviceInit,
         DrvClass* pThis);

     // 其他接口函数
     // ……
    }
    要能够通过静态函数回调成员函数,即通过PnpAdd_sta回调PnpAdd函数。前提是要能够获得对象指针,因为我们已经把对象指针保存在驱动对象的环境块中了,所以达到此目的不是难事。代码如下:
    NTSTATUS DrvClass::PnpAdd_sta(IN WDFDRIVER Driver,
            IN PWDFDEVICE_INIT DeviceInit)
    {
     // 取得环境块
     PDRIVEDR_CONTEXT pContext = GetDriverContext(Driver);

     // 环境块中存有对象指针
     DrvClass* pThis = (DrvClass*)pContext->par1;

     // 再调用成员函数
     return pThis->PnpAdd(Driver, DeviceInit);
    }
    所有其他的事件函数,都必须采用相同的方法实现。
    6.2.3  KMDF驱动实现
    其实上面的内容,一直是围绕KMDF进行讲解的。DrvClass内部的DriverEntry成员函数已经讲解过了,现在看看真正的入口函数该如何定义吧。
    extern "C" NTSTATUS DriverEntry(
         IN PDRIVER_OBJECT  DriverObject,
         IN PUNICODE_STRING  RegistryPath
         )
    {
     // 动态创建对象,此步在后面将被修改
     DrvClass* myDriver = new(NonPagedPool, 'CY01')DrvClass();
     if(myDriver == NULL)return STATUS_UNSUCCESSFUL;
     return myDriver->DriverEntry(DriverObject, RegistryPath);
    }
    干净得不得了,驱动程序在加载之初就以快捷无比的速度向我们定义的类靠拢了。至于第1行代码动态创建对象的操作,当前这样实现已经完全可以了,但在后面将被修改,以支持多态。
    6.2.4  WDM驱动实现
    如果使用WDM方式进行类封装,对于非PNP类驱动,可以在入口函数中创建控制设备对象,并把类对象保存在设备对象的设备扩展中;对于PNP类驱动,应当在AddDevice函数中建立设备栈时创建类对象,并将其保存在功能设备对象的设备扩展中。笔者会以前者为例,简单讲一下实现。WDMClass示例工程,读者参照代码,在它的基础上很容易扩展出功能更为完善的驱动程序。
    这里列出具体的封装过程。首先是类定义,定义一个通用的分发函数如下:
    class WDMDrvClass{
    public:
     static NTSTATUS  DispatchFunc_sta(
       DEVICE_OBJECT  Device,
       PIRP Irp);

     virtual NTSTATUS  DispatchFunc(
       DEVICE_OBJECT  Device,
       PIRP Irp);

     // 其他……
    };
    同理,定义一个静态函数和一个类成员函数,静态函数将通过对象指针调用成员函数。入口函数中要这样定义:
    typedef struct{
    WDMDrvClass pThis;
    //……
    }DEVICE_EXTENSION;

    NTSTATUS DriverEntry( PDRIVER_OBJECT Driver,
          PUNICODE_STRING Register)
    {
    // 创建动态对象
    WDMDrvClass* pDrv = new(NonPagedPool, 'SAMP') WDMDrvClass();

    // 设置分发函数,全部指向DispatchFunc_sta
    for(int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
       Driver->DispatchFunction[i] = pDrv->DispatchFunc_sta;
    }

    // 创建控制设备对象,并同时创建设备扩展区
    IoCreateDeviceObject(..., sizeof(DEVICE_EXTENSION));

    // 把对象指针保存到设备扩展中
    DEVICE_EXTENSION* pContext = (DEVICE_EXTENSION*)DeviceObject->DeviceExtension;
    pContext->pThis = pDrv;
    return STATUS_SUCCESS;
    }
    这一切就绪之后,我们还是来看看DispatchFunc_sta该如何实现吧。诚如我们所知,所有的驱动分发函数的第一个参数总是设备对象,正是我们所创建的那个。通过它,我们总是能够在静态函数中得到对象指针。下面是DispatchFunc_sta函数的实现。
    NTSTATUS  WDMDrvClass::DispatchFunc_sta(
       DEVICE_OBJECT  Device, PIRP Irp)
    {
      PDEVICE_EXTENSION pContext = Device->DeviceExtension;
      WDMDrv pThis = pContext->pThis;
      return pThis-> DispatchFunc(Device, Irp);
    }
    与上述KMDF的实现类似,其他更详细的实现内容,请参阅工程代码。
    6.3  多态
     如果纯粹是为了尝鲜,在驱动中加入一个类,内部却只是一团硬板,那就完全多此一举了。所以本节笔者将带领大家在内核中实现类的多态。以CY001 USB设备驱动为例进行讲解,代码请参考本书工程UsbBaseClass和CY001UsbClass,前者以基类实现设备驱动,后者以子类实现设备驱动。
    6.3.1  基类、子类
    笔者对基类的要求是能够实现USB设备的最基本要素,使得设备能够在系统中显现,能够正常运行和移除。所以设备栈一定要成功建立,基本的Pnp/Power接口也必须要提供,但用户层接口可以暂不考虑。最终的结果是PnpAdd函数实现得非常完整,因为必须要将设备栈建立起来;EvtDevicePrepareHardware和EvtDeviceReleaseHardware函数也得以完整实现,这样设备能够正确运行和移除,但细节方面的设置如休眠等则以接口留出。
    子类必须实现更完善的功能,如休眠、唤醒设置。下面的例子分别对应着基类和子类的实现。
    // 配置设备驱动的电源管理功能
    NTSTATUS DrvClass::InitPowerManagement()
    {
     return STATUS_SUCCESS;
    }
    这是基类的实现,空空如也,子类则要复杂许多倍。
    // 配置设备驱动的电源管理功能
    NTSTATUS CY001Drv::InitPowerManagement()
    {
     NTSTATUS status = STATUS_SUCCESS;
     WDF_USB_DEVICE_INFORMATION usbInfo;

     KDBG(DPFLTR_INFO_LEVEL, "[InitPowerManagement]");

     // 获取设备信息
     WDF_USB_DEVICE_INFORMATION_INIT(&usbInfo);
     WdfUsbTargetDeviceRetrieveInformation(m_hUsbDevice, &usbInfo);

     // 设置设备的休眠和远程唤醒功能
     // …… 详见代码

     return status;
    }
    6.3.2  实现多态
    怎么能够实现多态呢?当前,动态对象是在入口函数中创建的,而按照现有逻辑,入口函数是不允许修改的。笔者要提供一个机会,让库使用者可以创建动态对象。为此笔者特地有一个规定,所有库使用者必须定义一个宏,以注册自己的驱动类。
    REGISTER_DRV_CLASS(DriverName)
    如果不使用子类,则需要定义下面的宏而直接使用基类。
    REGISTER_DRV_CLASS_NULL()
    那么这两个宏到底有什么作用呢?要看宏定义了:
    // 注册子类
    #define REGISTER_DRV_CLASS(DriverName) DrvClass* GetDrvClass(){ return (DrvClass*)new(NonPagedPool, '10YC') DriverName();}

    // 注册基类
    #define REGISTER_DRV_CLASS_NULL()DrvClass* GetDrvClass(){ return  new(NonPagedPool, 'ESAB') UsbBaseClass();}
    两个宏都是为了定义一个名称为GetDrvClass的函数。前者注册驱动类的子类,并在GetDrvClass的实现中动态创建子类对象,在返回时将子类对象的指针转换为基类对象指针;后者则声明直接使用基类,并在GetDrvClass的实现中动态创建一个基类对象,并返回其指针。
    调用者不必关心被创建的对象到底是来自基类还是子类,他只要使用在基类中定义的接口就可以了,而借助虚拟函数的运行时绑定策略,即可实现多态。
    需要注意的是宏REGISTER_DRV_CLASS(DrvClass),其作用和REGISTER_DRV_ CLASS_NULL()是一样的,都将定义一个GetDrvClass函数。
    好戏还要看DriverEntry()函数中的实现,重新修改后的函数代码如下:
    NTSTATUS  Driver(DRIVER_OBJECT Driver,
        UNICODE_STRING Register)
    {
      DrvClass* pDriver = GetDrvClass();
      return pDriver->DriverEntry(Driver, Register);
    }
    真是无与伦比的简洁,它通过GetDrvClass函数实现了多态,并立刻将驱动的实现交付到了pDriver对象的手中,而pDriver可以是基类,也可以是任意一个从基类继承的子类。
    实现多态的核心是两个类注册宏,以及在入口函数中对GetDrvClass函数的调用。需要注意的是,如果用户同时定义了两个宏,那么系统就会因为发现两个完全一样的GetDrvClass函数而使编译失败;反之,如果上述两个宏一个都没有定义,那么在链接时,将因为无法找到函数定义而链接失败。
    驱动工程UsbBaseClass使用驱动基类直接驱动CY001 USB设备,从SOURCE文件中可以看到,它含有的编译文件为DrvClass.cpp和GetDrvClass.cpp两个文件,前者是基类的定义文件,后者只有一行代码,即REGISTER_DRV_CLASS_NULL()。这是最简单的驱动工程。
    驱动工程CY001USBClass使用驱动子类CY001DrvClass驱动CY001 USB设备,从SOURCE文件中可以看到,它依旧包含了DrvClass.cpp文件,此外还包含了若干个子类的实现文件。
    所以,读者只要在DrvClass甚至CY001DrvClass类的基础上实现子类化,并注册新的子类,就能够实现功能扩展。
    如图6-6所示是本节所讲的多态实现原理图。

    图6-6  多态关系图
    6.3.3  测试
    编译UsbBaseClass工程的代码,用得到的CY001.sys文件替代system32\drivers目录下的同名文件,以驱动CY001 USB设备。尝试使用本书中的UsbKitApp程序,会发现能够正确枚举到USB设备,但软件的具体功能如获取描述符等,无法正常使用。
    以同样的方法测试编译CY001UsbClass工程后得到的CY001.sys文件,并运行UsbKitApp程序以测试,会发现和WDFCY001工程的测试结果完全一样。
    6.4  小结
    使用本章中介绍的方法,可以轻松实现驱动的类封装。特别是本章介绍的实现多态的方法,可以使得驱动代码的复用性得到很大增强。建议读者在CY001UsbClass的基础上,再子类化一个MyCY001Ex(或其他你喜欢的名字)类,在父类的基础上添加自己的功能,并尝试使用编译这个新工程生成的sys文件,看新类能否发挥作用。

    ----------------------------------------------

    -----------------------------------------------

    第十二章第一节《用ROR创建面向资源的服务》
    第十二章第二节《用Restlet创建面向资源的服务》
    第三章《REST式服务有什么不同》
    InfoQ SOA首席编辑胡键评《RESTful Web Services中文版》
    [InfoQ文章]解答有关REST的十点疑惑

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2011/2/15 11:20:00
     
     hjx_221 帅哥哟,离线,有人找我吗?
      
      
      威望:7
      等级:博士一年级
      文章:4607
      积分:24021
      门派:XML.ORG.CN
      注册:2004/8/30

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给hjx_221发送一个短消息 把hjx_221加入好友 查看hjx_221的个人资料 搜索hjx_221在『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』的所有贴子 引用回复这个贴子 回复这个贴子 查看hjx_221的博客3
    发贴心情 
    学习一下
    谢谢

    ----------------------------------------------
    初从文,三年不中;后习武,校场发一矢,中鼓吏,逐之出;遂学医,有所成。自撰一良方,服之,卒~ 
    http://hjx221.blogger.org.cn/

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2011/6/4 22:17:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客4
    发贴心情 
    这本书很不错,学习了。。。。

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2011/10/13 11:59:00
     
     sdk1980 帅哥哟,离线,有人找我吗?白羊座1981-3-21
      
      
      等级:大一新生
      文章:6
      积分:74
      门派:XML.ORG.CN
      注册:2011/11/6

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给sdk1980发送一个短消息 把sdk1980加入好友 查看sdk1980的个人资料 搜索sdk1980在『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』的所有贴子 访问sdk1980的主页 引用回复这个贴子 回复这个贴子 查看sdk1980的博客5
    发贴心情 
    看着真是费神哈
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2011/11/6 10:43:00
     
     GoogleAdSense白羊座1981-3-21
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 HTML/XHTML/Ajax/Web 2.0/Web 3.0 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/4/30 14:44:26

    本主题贴数5,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    5,371.094ms