除了外挂和qq红包继续发送功能还有别的方法赖包么?

微信,是一个生活方式 超过十亿人使用的手机应用 支持发送语音短信、视频、图片和文字 可以群聊,仅耗少量流量,适合大部分智能手机
微信 2.3.6 for Mac 发布
微信 for iOS / Android
微信 2.6.2 for Windows 发布
微信网页版 扫一扫二维码 就能在浏览器上使用微信
微信 Mac 版 极致简洁,迅捷沟通
微信 Windows 版 让沟通更方便外挂是什么?_百度知道
外挂是什么?
我有更好的答案
游戏中的外挂说好听了就是游戏辅助器,说不好听了就是作弊器像CS游戏中透视挂,自动瞄准挂。。。等等,典型的外挂作弊器也有坦克世界中插件,这也是外挂,是游戏代理商默认存在的辅助软件,当然了,这种外挂不会破坏游戏平衡,最多只是辅助而已开挂在不同的领域有不同的额意思..就不知道您说的是哪类了..1、在游戏中是种破坏平衡性的软件..2、在动漫中是指主角光环定律..3、在生活中甚至可以理解为走后门..望采纳
采纳率:41%
谓的外挂就是指一种第三方软件(不是所有第三方软件都是外挂的,比如插件),网络游戏多是基于Internet上客户端/服务器模式(C/S模式),服务端程序运行在游戏服务器上,游戏的设计者在其中创造一个庞大的游戏空间,各地的玩家可以通过运行客户端程序同时登录到游戏中。简单地说,网络游戏实际上就是由游戏开发商提供一个游戏环境,而玩家们就是在这个环境中相对自由和开放地进行游戏操作。那么既然在网络游戏中有了服务器这个概念,我们以前传统的修改游戏方法就显得无能为力了。记得我们在单机版的游戏中,随心所欲地通过内存搜索来修改角色的各种属性,这在网络游戏中就没有任何用处了。因为我们在网络游戏中所扮演角色的各种属性及各种重要资料都存放在服务器上,在我们自己机器上(客户端)只是显示角色的状态,所以通过修改客户端内存里有关角色的各种属性是不切实际的。那么是否我们就没有办法在网络游戏中达到我们修改的目的?回答是&否&。除了修改游戏封包来实现修改游戏的目的,我们也可以修改客户端的有关程序来达到我们的要求。我们知道各个服务器的运算能力是有限的,特别在游戏中,游戏服务器要计算游戏中所有玩家的状况几乎是不可能的,所以有一些运算还是要依靠我们客户端来完成,这样又给了我们修改游戏提供了一些便利。比如我们可以通过将客户端程序脱壳来发现一些程序的判断分支,通过跟踪调试我们可以把一些对我们不利的判断去掉,以此来满足我们修改游戏的需求。游戏外挂最原始的概念是:一种可以自动挂机进行游戏(包括打怪、升级、补血……)的第三方件软件,它可以让玩家不亲自进行游戏就可以让角色自动升级。编辑本段运行原理外挂一般是指在电脑运行中,一个程序通过某种事件触发而得以挂接到另外一个程序的空间里(常用的触 发事件有键盘触发,鼠标触发,消息触发等),挂接的目的通常是想改变被挂接程序的运行方式。现在的 游戏外挂就是将外挂程序嫁接到游戏程序当中,通过截取并修改游戏发送到游戏服务器的数据而实现各种 功能的增强。Internet客户/服务器模式的通讯一般采用TCP/IP通信协议,数据交换是通过IP数据包的传输来实现的,一般来说我们客户端向服务器发出某些请求,比如移动、战斗等指令都是通过封包的形式和服务器交换数据。那么我们把本地发出消息称为SEND,意思就是发送数据,服务器收到我们SEND的消息后,会按照既定的程序把有关的信息反馈给客户端,比如,移动的坐标,战斗的类型。那么我们把客户端收到服务器发来的有关消息称为RECV。知道了这个道理,接下来我们要做的工作就是分析客户端和服务器之间往来的数据(也就是封包),这样我们就可以提取到对我们有用的数据进行修改,然后模拟服务器发给客户端,或者模拟客户端发送给服务器,这样就可以实现我们修改游戏的目的。打个比方说,在正常情况下,我控制着一个游戏角色,跳了一下,假设我跳了一米,那么这就是正常的数据传输,通过我的操作,客户端向服务器发送了一个跳一米的数据,服务器作出回应,这样的话我就只能跳一米,但是一但安装了外挂软件,那么外挂软件会自动拦截这个跳一米的数据,做了修改,修改成了跳一百米的数据传送给服务器,这样的话服务器接收到的数据是跳一百米的数据,从而就可以跳一百米,你想想,别的玩家只能跳一米,使用外挂的能跳一百米,这就严重破坏了游戏的公平性,不仅如此,由于本身服务器设计只能跳一米,但是被外挂修改,传输了个跳一百米的数据,这样的话服务器就需要腾出空间来传输跳一百米的数据,这样会导致服务器工作量激增,服务器端垃圾数据增多,会影响其他玩家的体验,危害极大!编辑本段基本模块1。自动化管理,外挂挂机的主要功能。2。减小(增大)游戏指令与指令之间的间隔时间,例如:快速战斗,快速劳动等(非变速齿轮的直接加快游戏速度的效果)。3。执行一些因为玩家个人游戏信息的问题而在客户端无法直接执行的命令,但这个命令在客户端却是允许的(例如等级1就执行等级99级才能执行的指令)。4。修改客户端正常的指令发送至服务器达到一定的特殊效果,这种指令是一般客户端不能编译发送的,但服务器却可正常接收执行,如:发送GM的系统消息。又如:把一个在游戏中价值10的物品卖成100000,简单的举个例子,例如这个物品代号为a,原本应发送。a。10这个数据,但通过外挂把这个数据改为a。100000这样就把10的东西卖到100000。制作外挂就是大量收集正常数据指令并观察其规律,之后通过修改并集中这些数据指令制作出外挂的各模块,之后编成一个软件。编辑本段使用目的应该说早期图形网络游戏(如uo、kok)的外挂可以说是出于善意的,外挂机器人只是代替线上玩家进行某些重复性动作,以达到长时间在线&练功&的目的,可以使一些忙于工作的人也能够享受到网络游戏的乐趣,网络游戏服务商对此也是睁只眼,闭只眼,因为他并没有对网络游戏规则造成太大的冲击.现在的外挂已经不仅仅是重复性机器人而已:如&加速器外挂&可以大幅度修改客户端ID的移动速度;&经验外挂&可以在游戏中向服务器发送npc本身xx倍的经验的封包,以达到迅速成长的效果;更有甚者可以对服务器端的id或物品进行属性修改……,网络游戏蒸蒸日上,而网络外挂也是如火如荼,似乎网络外挂与网络游戏的争端从有网络游戏就开始了,越是玩家聚集的游戏其外挂现象就越是严重,游戏外挂软件的多寡已经成为评价一个网络游戏成功与否的标准。甚至有玩家戏称:“没有外挂的游戏是网络垃圾”,虽然很多游戏不免有许多外挂,但一定不要使用,可以这么说,每个外挂都有绑定木马,轻的话你的账号会被盗,重的话会导致电脑死机瘫痪。编辑本段分类授权外挂授权的外挂即(内挂)软件在合理性、使用性、安全性上都是最优秀的,由于开发外挂需要考虑自身体积、人机功效及可靠性,所以好的外挂一般都是先由多个人或组织开发各式各样的!由官方测试后认定某一单位开发的外挂可以安全、稳定的给用户服务,这样官方才会授权它可以公开出售、下载及使用!未授权外挂未授权外挂并不是违法的,授权外挂的前身都是未授权外挂,它们需要在主体软件上作全面的测试,不然很容易出现死机、毁数据或其他有害症状!未授权外挂不可以随意发放给用户,更不能作为商业用品交易,所以法律为了软件用户的数据安全规定:出售未授权外挂是违法的的行为!另外如果未授权外挂被人利用来传播病毒和木马,那对用户来说就是一场灾难!编辑本段外挂影响单机游戏单机游戏外挂,令很多不是游戏高手的玩家,可以很轻易完成游戏。有的单机游戏外挂则可增加游戏中对玩家有利的功能,从而令该游戏的玩法变得更容易。 但是,有些来路不明的外挂,在转散布的过程中,被恶意植入病毒或木马。网络游戏外挂会造成网络游戏的极度不公平,同时造成服务器端的垃圾数据增多(这通常是因为外挂软件开发者没有掌握服务器端的技术而造成的),而且由于使用外挂者大多不用在电脑前加以控制,而令玩家长期处于“挂机”状态,服务器需要使用更多资源来处理这些并非由人控制的角色,令到服务器端的工作量激增,网络游戏运营商需要打开更多服务器来处理这批角色,而使成本增加。(都有可能令其他玩家游戏画面或速度减慢)一些受欢迎网络游戏就会有“专业”的外挂。一些比较少玩家的网络游戏通常都没有“专业”的外挂,因此,外挂的多少可以作为一个网络游戏受欢迎程度的指针。编辑本段外挂危害外挂会修改、破坏游戏数据,严重的可以造成游戏数据丢失,游戏速度缓慢。账号密码一些外挂程序在注册时,都会提示玩家输入账号和密码。当玩家输入游戏的账号和密码后,很有可能发生丢号事件、丢失虚拟物品事件。还有部分外挂程序制作分子利用玩家贪图一时便利而注册外挂的途径,大肆实施窃取他人账号行径。更有甚者打出了免费的旗帜,当玩家在使用一定时期后,逐渐依赖外挂而游戏时,再收取费用。暗藏病毒外挂程序非游戏开发商制作,其内容得不到任何审批和保证,很多外挂制作者在外挂软件中安装木马等病毒程序,专门盗取用户的计算机信息资料(包括用户上网资料及游戏登陆资料等)。由于外挂程序不在合法软件的保护范围之内,感染病毒造成的损失后果,需由玩家自己承担。运行外挂的同时,很多时候会出现游戏卡机、电脑程序受损、电脑无法启动等情况。乐趣丧失玩家赖于外挂的帮助,长时间挂机升级,导致无法体验到游戏的乐趣和精彩,同时也损害游戏世界的公平性,破坏好友间的感情、友谊,更影响了其它玩家的快乐体验。没有外挂的干扰,玩家才能更投入、尽情的游戏。编辑本段外挂分类网络游戏外挂也有合法的,比如网络游戏自动更新后的新功能或补丁,它们也都是外挂实现的,而非法的网络游戏外挂则是为了某些个人利益研制的入侵程序,其实它们并不叫外挂,他们应该叫权限提升程序,它可以使用户得到法定他们不能使用的权限,这和外挂的本质区别很大,为什么软件商会禁止我们用这些权限呢?因为有了这些权限包含一些危险的操作,甚至执行一个错误的操作你就可能毁掉整个硬件系统!辅助外挂
外挂示意图以辅助玩家游戏为目的的,实现更加便捷方便的玩游戏,主要因为的游戏操作过于复杂,过于单调,使用玩家们都想需要这么一款辅助软件来帮助游戏,该工具不具有修改游戏数据、破坏游戏功能。而且现在很多游戏都已经自带“外挂”了。只有这类辅助工具是合法的。辅助外挂主要通过获取游戏句柄,通过颜色判断或内存判断游戏中角色的生命法力等数据,实现自动补给功能。还有一些例如答题等配合游戏中的任务辅助工具。的外挂已经是多样化,真正的破坏游戏的外挂几乎只有违法的收费外挂了。变态外挂
变态外挂示例图变态外挂完全破坏了游戏的平衡性,利用了游戏自身存在的受限功能,使得突破其限制,让任何一个使用该程序的玩家都能实现一些游戏中正常无法实现在变态行为。如在游戏中:穿墙、飞天、吸怪、无敌、加速、加倍攻击等严重破坏平衡性的。该类型外挂通常是收费的,属于违反国家法律的,坚决禁止的,希望广大玩家自觉抵制外挂!脱机外挂脱机顾名思义就是脱离客户端程序,在前期的脱机版外挂都是带有窗口的外挂,随着发展终于可以不依赖官方发布的客户端程序就可以运行的外挂了。对于的不依赖客户端的脱机版的原理说起来很简单,就是了解了这个游戏的客户端和服务器之间的通讯的数据包的几乎全部内容以后,做一个外挂程序,可以模拟官方的客户端进行登录、游戏。并且实现官方客户端所没有的功能,比如:自动打怪、捡东西、交易等等。做这样的脱机外挂一般来说,需要了解很多游戏的内部技术资料,光靠自行摸索是很难的。 脱机外挂很大程度上是游戏公司内部的人员自己做的或者是窃取了商业机密。编辑本段缺点当然这种观点有失偏颇,但外挂软件的确从另一个层面反映了网络游戏的受众程度。一个网络游戏,玩的人多了,外挂就会紧跟着来。龙族、魔力宝贝、天使、传奇等等无一幸免。奇迹的外挂似乎来得更快,快到点卡还未上市,外挂卡已经开始卖了。外挂软件给部分玩家带来刺激与兴奋之后,也破坏了游戏规则,这类的外挂已经严重影响了游戏的公平性,致使其他玩家无法与使用外挂的玩家进行抗衡,于是越来越多的玩家离开了游戏,网络游戏的运营商也逐步丧失了市场。因此外挂软件损害了玩家的利益也损害了运营商的利益,从某种程度上说也破坏了网络经济的健康发展。外挂的坏处具体如下:外挂可以让别人在5分钟内做到你50分钟才能做到的事情 。这使玩家心理极不平衡。毕竟玩游戏就是玩个心情,心理不平衡了自然游戏也没什么乐趣。于是为了追求心理平衡,大部分玩家都选择用外挂,小部分玩家选择退出。而留下的那些使用外挂的玩家都可以用5分钟做到50分钟的事。于是一个游戏迅速消亡,退出市场,因为所有可玩的部分,外挂已经替你玩了。挂机外挂的原理也是差不多的。当你发现你一整天的努力别人只需要晚上睡觉时把电脑开着就可以,心理也会极度不平衡。那些更强力(或者说变态)的外挂危害则更大。挂机外挂或加速外挂只是间接的损害了其他玩家的利益,它帮助你可以轻松的完成其他玩家需要付出巨大努力的事,它只是打乱了游戏的金融秩序,使其他玩家的游戏币 物资 人物资料等贬值而已。而大部分变态外挂则是直接损害其他玩家利益。那些外挂基本都能使你做到其他玩家做不到的事。具体可以帮助你做到哪些其他玩家做不到的事,那就数不胜数了。而且有些制作外挂者在外挂中放置病毒或木马,来盗取玩家的游戏账号、密码,甚至破坏用户的计算机。外挂使用者的反驳:1.“是我玩游戏,不是游戏玩我,我只要享受游戏的乐趣就可以了。”2.“我实在是没时间做那些枯燥的事,我只能用外挂。”3.“我想解放我的双手,不把青春浪费在敲键盘上。”4.“如果经营商把游戏变的合理的话,就没有外挂了。”5.“你想开挂还不知道如何开呢。”6.“我实在是打不过对方,只能借助高科技来帮帮我。”编辑本段外挂定制外挂的定制,指的是商业行为中顾客对服务商提供的服务进行挑选,以符合成本最低的情况下达成目标。也就是个性定制(用户获得自己定制的个人属性强烈的商品)。编辑本段相关知识普通外挂1.通常使用外挂70%的人经常狩猎不主动攻击的怪,而且几乎就在同一点位置打坐休息2.挂机者一般都关闭了组队邀请,决斗邀请,假如他没关闭的话,而你又怀疑他挂机的话你可以邀请和他组队,他会半天没反应的,或者你可以尝试和他说话,他也没反应的,你可以尝试用这方法去识别,正确率99%以上3.挂机者一般开了2-5倍的攻击速度,打怪出招动作很利索,非常明显(你和挂机者同职业的时候你可以比较下攻击速度)4.挂机者一般出没于凌晨的时候~他们通常节约时间不做任务,一直挂机打钱练级的~5.有时候在野外看到某个人一直往死角里跑,或者一直爬山坡爬不上去,那是因为角色死了,外挂自动会跑到练级点,但跑得路线没那么智能,所以我们能看到这情况,城里也能看到某个死角,某个商店1个人在不停地跑那就是外挂100%没错的!竞速外挂1、不用加速时就可以甩你!2、一旦加速,他的车身的火光持续时间N长!3、由于速度过高,转弯时很不稳定。4.看速度. 如果第一名的速度奇快..那一定是外挂!!5.看名字后面的系统检测度(网速条). 在比赛的时候.你们一定注意到了每个人的名字后面都有几个或红或绿的小竖杠!!那个就是系统的自动检测度.如果那个是红色就表示有可能是外挂(PS:是有可能)6.看玩家.这个是很实用的方法.如果你看见有玩家是在原地不动的.而且排名不会改变.一直是最后.但是最后赢得比赛的却是他..那么有一定几率是开挂的.(PS:是有一定几率)7.在喷了一次时候就开始无限加速的!!此绝对是外挂.!!!8.系统检测度为红色.玩家原地不动全部出现者.就是玩家原地不动.而且检测度为红色者.100/100是外挂.此外挂为挂机外挂.无法破解.最后的结果是原来的第一名完成比赛后成为了第二名.而他/她却成为了第一名.有个破解办法..但是还是不太有用。.就是如果你发现了.只要撞一下他.他飞了就可以了.这样做的话只能让他得第二名.第一名和他会同时完成.但是显示的是他在第二名。注:1、有相当一部分外挂玩家极力掩饰自己的无耻行径.他们通常会采取故意起跑落后、用刹车降低速度,甚至故意输掉一两局来让大家觉的他的速度不快。但是这其实是行不通的,狐狸总会漏马脚,毕竟用外挂的目的还是要赢得比赛。2、有人说数秒时就有人先起跑了,这个不能算作看是否使用WG的方法之一,引起这种情况的通常还会和网络问题有关。竞速赛比得是时间。只要你自己机子上的时间比别人快,就算他跑第一也得不到真正的第一。判断CS外挂1、在刀战中,别人一靠近你瞬间就可以把你杀死的,那肯定是用了小刀加速挂。2、当你看到一个人从一边突然飙到另一边(瞬间,但飙之前有一定时间停顿)而且他的延迟在这一瞬间达到了200以上,不要怀疑,这就是瞬移挂。3、当你在很短的时间听到连续的重狙声(像步枪一样快)的,那么就是他开了子弹加速挂。4、当你还没看见别人就被穿墙的子弹+爆头,除非运气好,如果出现几次,就很可能开了外挂(一般是cs007)。5、当一般模式下别人掏出很多个手雷或闪光之类的,一定是开了外挂。生命和防弹都不减少,或者减少了又增加(一般是爆头不死、狙不死),那一定是一种极为少见的外挂。编辑本段发展随着游戏官方对外挂的抵制,游戏本身也有了超强的自动检测外挂的功能,但制作外挂的技术也不断提高着,现在最流行的就是在游戏中用封包和抓包工具对游戏服务器提交假的数据从而改变游戏人物能力,例如对游戏人物增加攻击力,对身上的装备修改属性,在地下城与勇士和穿越火线游戏中用的最为广泛,用户利用外挂这种作弊手段可以轻易得到其他正常用户无法得到、或必须通过长期运行程序才能得到的游戏效果。外挂的功能还有很多种,有加速器、封包等,其最显著的特征就是为使用外挂的游戏者带来不同于正常用户的游戏效果,它能使使用外挂者比正常用户奔跑快、攻击威力加大、获得更多的经验值。外挂的最初意思是外部调用程序的通俗称法,接近于Windows的API(应用程序接口)。但外挂之于网络游戏来说,主要指那些可能对游戏的运行造成不良影响的相关程序。主要有自动工作、游戏加速、能力加强等功能。编辑本段武器外挂
武装直升机外挂反坦克导弹火箭发射巢外挂还有一个传统的解释是:单位形的作战武器系统(如直升机,坦克,战车等)在其原本的作战单位上添加附加的用于辅助作战单位作战的工具、器具等。如:阿帕奇直升机外挂响尾蛇导弹,那么这个导弹可称为直升机的外挂。或是战车外挂油箱,这个油箱可以使战车行驶更遥远的距离,那么这个油箱可称为辅助作战外挂。编辑本段运动外挂在户外运动中,把组织团队中的人在活动中带来的非组织成员,这个非组织成员称为带他参加活动人员的外挂,也就是说是组织成员的一部分,要负有一定的责任。编辑本段法律界定辅助工具辅助工具:利用系统允许的功能代替鼠标键盘输入直接调用目标程序中允许被用户执行的代码(例如模拟鼠标键盘,CALL等等)。他的主要特点是“使用自动化操作代替常规的鼠标键盘输入”,并且是未被游戏明确禁止的行为,或者是游戏本身支持和肯定的辅助工具。而外挂在法律上有明确的界定:破坏互联网游戏作品的技术保护措施,从而谋取利益,即有赢利之目的,又有破坏之行为才是外挂。而一些开发工具,例如VC、VB、汇编语言以及快手AAuto虽然可以用来开发外挂、但这些工具本身并不提供任何通过破解游戏作品技术保护措施从而谋取利益的行为,所以他们都不是外挂。举证问题1.玩家的举证义务(1)账号的真实身份。这是玩家主张权利的前提,即证明自己是该账号的拥有者,一般通过输入密码可以证明,但也有例外情形,玩家需要做相应的证明。(2)虚拟财产。得到虚拟财产付出的对价,包括金钱、时间等(3)精神损害赔偿有关的证据。主张精神损害赔偿比较困难,但如果玩家确实认为运营商封号对其造成了精神损害,则有义务证明造成了损害及损害造成了严重的后果。2.运营商的举证义务(1)运营商对游戏不拥有版权情形下:外挂侵权的证据、玩家使用外挂的证据、提供虚拟财产数据、合同条款;(2)运营商对游戏拥有版权情形下:提供拥有版权的证据、版权及其它权利受到侵害的证据、合同条款。以上只是对各种纠纷中举证义务一个概括,还要视纠纷情形的不同具体而定,以及何种情况下举证责任倒置问题也暂不在此论述。在笔者的《网游纠纷解决的程序设计》一文中将会对此再做详细论述。编辑本段更新速度外挂的更新速度,已经超过了网络游戏的更新速度,外挂制作者主要在网络游戏更新的时候,进入游戏(偷渡)寻找代码,所以外挂才能在第一时间更新。编辑本段法律政策外挂给许多厂商的利益造成了极大的侵害,包括国家新闻出版总署、信产部等在内的五部委曾经联合发布过《关于开展对“私服”“外挂”专项治理的通知》,开展过多次专项打击行动。在该通知中,明确指出了“私服”“外挂”行为的严重违法性,即“私服”“外挂”违法行为是指未经许可或授权,破坏合法出版、他人享有著作权的互联网游戏作品的技术保护措施、修改作品数据、私自架设服务器、制作游戏充值卡(点卡),运营或挂接运营合法出版、他人享有著作权的互联网游戏作品,从而谋取利益、侵害他人利益。重则可以判刑!根据我国著作权法之规定,使用他人作品,应当支付报酬而未支付的,应当根据情况承担停止侵害、消除影响、赔礼道歉、赔偿损失等民事责任。公安部颁布的《计算机信息网络国际联网安全保护管理办法》第六条规定:“任何单位和个人不得从事下列危害计算机信息网络安全的活动:(一)未经允许,进入计算机信息网络或者使用计算机信息网络资源的;(二)未经允许,对计算机信息网络功能进行删除、修改或者增加的;(三)未经允许,对计算机信息网络中存储、处理或者传输的数据和应用程序进行删除、修改或者增加的;(四)故意制作、传播计算机病毒等破坏性程序的;(五)其他危害计算机信息网络安全的。”外挂的使用修改游戏玩家和服务器之间传送的数据并且破解和使用服务器协议,直接违反了此规定的第六条第一项和第三项之规定。针对这种违法行为,《计算机信息网络国际联网安全保护管理办法》的第二十条规定:“违反法律、行政法规,有本办法第五条、第六条所列行为之一的,由公安机关给予警告,有违法所得的,没收违法所得,对个人可以并处五千元以下的罚款,对单位可以并处一万五千元以下的罚款,情节严重的,并可以给予六个月以内停止联网、停机整顿的处罚,必要时可以建议原发证、审批机构吊销经营许可证或者取消联网资格;构成违反治安管理行为的,依照治安管理处罚条例的规定处罚;构成犯罪的,依法追究刑事责任。”另外我国的《计算机软件保护条例》第二十四条之规定: 除《中华人民共和国著作权法》、本条例或者其他法律、行政法规另有规定外,未经软件著作权人许可,有下列侵权行为的,应当根据情况,承担停止侵害、消除影响、赔礼道歉、赔偿损失等民事责任;同时损害社会公共利益的,由著作权行政管理部门责令停止侵权行为,没收违法所得,没收、销毁侵权复制品,可以并处罚款;情节严重的,著作权行政管理部门并可以没收主要用于制作侵权复制品的材料、工具、设备等;触犯刑律的,依照刑法关于侵犯著作权罪、销售侵权复制品罪的规定,依法追究刑事责任:(一)复制或者部分复制著作权人的软件的;(二)向公众发行、出租、通过信息网络传播著作权人的软件的;(三)故意避开或者破坏著作权人为保护其软件著作权而采取的技术措施的;(四)故意删除或者改变软件权利管理电子信息的;(五)转让或者许可他人行使著作权人的软件著作权的。有前款第(一)项或者第(二)项行为的,可以并处每件100元或者货值金额5倍以下的罚款;有前款第(三)项、第(四)项或者第(五)项行为的,可以并处5万元以下的罚款。若外挂在未经得运营商许可的情况下截取数据流,使用服务器端软件构成对计算机信息网络中存储、处理或者传输的数据和应用程序进行删除、修改或者增加的的行为,不仅违反了我国的著作权法,而且违反了计算机软件保护条例。同时如果外挂主体触犯刑律的。
为您推荐:
其他类似问题
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。第十五章&SHELL扩展
谈到Windows Shell编程,Shell扩展是最重要的科目之一,绝大多数商业应用的最酷特征的都是通过Shell扩展实现的,而且有许多显著的系统特征实际都是插入了扩展代码。Shell扩展尤其令人激动的是它允许你把你的应用作为Shell的一部分来处理。
&&&&&&&&&Shell扩展的另一个好处是微软正在使它变得更聪明,例如,&查找&菜单,从Windows95&到Windows98&一直是通过Shell扩展增强的,而且增加了新条目。还有,出现在文档关联菜单上的位图项也是使用Shell扩展增加的。
&&&&&&&&&Shell扩展不仅是构建增加Shell功能模块的重要手段,而且也是使应用获得有力的Shell特征的重要方法。在前面各章中,我们讨论了系统集成方面Win32应用程序应该做的工作。我们探讨了关联菜单,图标,和几个其它方面技术。然而,这些都是静态和确定的。你可以设置或删除它们,然而,这些就是你所能做的全部:在这之间你不能做任何事情。因此,通向完全融入Windows的应用最后一步是要考虑编写一个或多个Shell扩展的可能性。注意,我说的&可能性&,事实上尽管Shell扩展是与Shell通讯的有力并且是灵活的方法,但是它并不是你和你的程序必须做的。
&&&&&&&&&在这一章中,我们将探讨所有Shell扩展的编程技术,并且提供某些有意义的示例,主要方向是:
&&&&&&&&&&&&&&&&&&&Shell扩展是什么,怎样与它们一同工作
&&&&&&&&&&&&&&&&&&&用C++&和ATL怎样写Shell扩展
&&&&&&&&&&&&&&&&&&&Shell扩展的排错方法
&&&&&&&&&&&&&&&&&&&使用Shell扩展定制关联菜单,图标,和属性
这章的最后部分将专注于文件观察器,严格地说,它们并不是Shell扩展,但是它们有类似的内部结构。文件观察器是一个程序模块,它可以使你能快速预览给定类型的文档而不需要借助建立和管理那种类型文件的应用。文件观察器通常与关联菜单的&快速观察&项关联。
Shell扩展:类型和提示
&&&&&&&&&Shell扩展是一个进程内COM服务器,它在探测器需要时被加载。Shell扩展不是一个全新的概念,它只比Wondows3.1的文件管理器外挂多了一点点东西。然而,Shell扩展使用了COM体系结构而不是DLL函数集,并且给出更广泛的功能范围。
什么是Shell扩展
&&&&&&&&&正象上面提到的,Shell扩展是实现COM接口的进程内COM服务器。你需要编写模块,注册它到注册表,并运行探测器窗口实例来测试它。不必着急知道什么时候,怎样或由谁来调用它&&倘若你正确地注册了它,这些是自动发生的。Shell扩展是DLL,可以放在PC的任何地方。就象任何其它COM服务器一样,它输出四个全程函数,通过这些函数,客户端模块可以识别和连接到这个服务器:
DllGetClassObject()
DllCanUnloadNow()
DllRegisterServer()
DllUnregisterServer()
除此之外,Shell扩展还需要提供通常COM的一些接口,如类工厂和IUnknown接口的实现。最后它还必须实现需要与Shell交互的接口。
调用Shell扩展
有一定数量的探测器可识别事件是可经由客户模块定制的,例子是探测器显示关联菜单或属性页,绘制图标或拖拽文件操作,也就是说,在执行一种文档的特殊任务时,探测器查找注册的用户模块,如果找到,则连接这个模块并调用要求的接口方法。这个关系看上去有点象Windows初级编程所描述的回调机理。回调是预定义原型的函数(通常有推荐的行为),服务器模块将调用这个回调函数以使客户可以插入响应给定的事件。Windows API的枚举函数EnumWindows()就是一个极好的例子。对于Shell扩展所发生的情形概念上与此完全类似。
文件管理器的外挂
&&&&&&&&&文件管理器的外挂正好依赖于回调函数,在加载时,文件管理器扫描它的winfile.ini文件查找&外挂&节的DLL名:
MyExtension=C:/WINDOWS/SYSTEM/FMEXT.DLL
在这个DLL中文件管理器希望找到FMExtensionProc()函数,其原型为:
LRESULT CALLBACK FMExtensionProc(HWND hwnd, WORD wMsg, LPARAM lParam);
此时,管理器开始发送消息到这个函数。通过编写这样一个函数,你就能够添加新工具条按钮,被通知选中状态,修改菜单,和作其它操作。如果你愿意,可以参考Internet客户端SDK资料。
从文件管理器的外挂到Shell扩展
&&&&&&&&&我们已经有了文件管理器外挂导出操作的概念,现在可以把这个概念转换到Shell扩展。这里主要的结构差异是:
&&&&&&&&&&&&&&&&&&&代替单一回调函数的是COM接口
&&&&&&&&&&&&&&&&&&&代替INI文件的是一批注册键和值,它们关联到扩展的文件类型
&&&&&&&&&&&&&&&&&&&代替简单DLL的是COM服务器
所以,尽管有一些无可否认的类似性,文件管理器的外挂与Shell扩展是两个根本不同的概念。技术范围已经改变:文件管理器外挂是应用为中心的,信息交换很少考虑单个文件,并且不识别文件类型。Shell扩展分别施加于每一种文件类型&&它们是为这种活动方法而专门设计。
探测器怎样导入Shell扩展
&&&&&&&&&为了理解探测器与Shell扩展之间的交互作用,让我们调查一个实际情况。在这个工作完成后你就能清楚地理解这些操作怎样互相作用,以及为什么Shell扩展要这样设计。
&&&&&&&&&我们前面提到过,在进一步处理特定任务集之前,探测器在注册表的某个地方寻找注册模块。它装入找到的所有扩展,并且调用它们的方法。为了获得一定的行为,只需适当地注册模块。要禁止它就要注销这个模块。
&&&&&&&&&要探查的注册表确切路径和扩展的编程接口可以各不相同,这依赖于探测器触发调用所引起的事件。
显示关联菜单
&&&&&&&&&看一个典型的例子:显示特定文件类型&&位图(bitmap)的关联菜单。用户在Shell观察下右击BMP类型文件时这个过程启动。关联菜单由不同的项目组构成,首先是系统标准项如&拷贝&,&剪切&,&建立快捷方式&和&属性&。然后是文档特有的动词,这是静态附加的。再有就是所有文件附加的通用动词,不管是什么类型的文件都有这些项。第四组是来自关联菜单Shell扩展的项,这是为特定类型文件而注册的扩展,此时是位图文件。
&&&&&&&&&当探测器建立弹出菜单时,它启动所有附加的标准项,和每一个注册表中的项,然后它在相关文件类型的ShellEx键下查看(如果存在),搜索ContextMenuHandlers子键。对于BMP,其形式为:
HKEY_CLASSES_ROOT
/Paint.Picture
/ContextMenuHandlers
位图的主键是Paint.Picture,微软的Paint是一个管理位图的程序。这是默认的,除非你安装了不同的图像软件。
&&&&&&&&&在ContextMenuHandlers键下,默认值包含实现扩展的COM&服务器的CLSID。知道了这个CLSID后。探测器模块装入它到自己的内存空间。这就完成了服务器实例的建立,并且查询扩展所要求的接口。对于关联菜单,接口是IContextMenu,这个接口包含了添加新菜单项的方法,恢复在状态条上显示的描述串,和执行响应用户点击的一些代码。
&&&&其工作过程是:探测器首先唤醒IContextMenu::QueryContextMenu(),来请求模块添加新菜单项。每当新菜单项被选中,探测器都调用GetCommandString()来获取显示在状态条上的描述。最后,当有点击发生在客户菜单项上时,运行InvokeCommand()来提供运行时的行为。这些由探测器唤醒的函数可以提供在Shell中定制菜单项的手段,当然还需要严格地按规定注册。后面我们将深入的研究这些方法。
Shell扩展的类型
&&&&&&&&&我们反复提到Shell扩展是在Shell响应特定事件集时被装入的。因此,有固定数量的Shell扩展,即有输出不同函数的COM接口集来影响特殊的情况。显示关联菜单不同于绘制图标,或显示属性对话框,所以不同的COM接口做不同的工作也就不奇怪了。
&&&&&&&&&Shell扩展的类型是:
IContextMenu
允许添加新项到Shell对象的关联菜单
IContextMenu
允许添加新项显示在右键拖拽文件后的关联菜单上
IExtractIcon
可以在运行时决定在一个文件类中给定文件应该显示的图标
IShellPropSheetExt
可以附加属性页到文件类的属性对话框,对控制板小程序也能工作
可以控制任何通过Shell的文件操作。在允许或拒绝时不需告知成功或失败。
IDropTarget
可以决定在Shell中当对象被拖动(使用鼠标左键)到另一个之上时需要做什么
IDataObject
可以定义对象怎样拷贝到剪裁板或怎样从剪裁板抽取对象
编写Shell扩展
&&&&&&&&&编写Shell扩展就如同编写进程内COM服务器一样,这没有什么可奇怪的。你必须提供基本的COM素材,实现接口,适当地注册服务器,以及随后的测试和排错。与任何开发过的其它COM模块一样,其中含有大量的重复且很少改动的代码,这些代码本身已经封装在某些C++&类中。因此我们可以预知下一步将要干什么。
&&&&&&&&&我们建议使用ATL作为开发Shell扩展的工具,毕竟,现在的ATL是C++&开发COM服务器最好的工具,而且Shell扩展本身就是ATL结构的。微软活动模版库是特别设计用于简化开发COM模块的,而且远比MFC先进。
第一个Shell扩展
&&&&&&&&&现在是我们编写Shell扩展的时候了。Shell扩展实际是相当简单的对象,就象开发玩具一样,即使是头一个要开发的,也是如此。我们将从完成前一章的Windows元文件和增强元文件的例子开始。目标是展示怎样添加客户页面到WMF和EMF文件的属性对话框。
添加属性页
&&&&&&&&&直接在属性页预览元文件是不是更好一点。确实,你可以从文件夹的&观察&|&作为Web页面&的选项打开所选择的文件进行预览,但是,如果你不知道或不想要这个观察时会怎么样。此外,如果你还运行在Windows95或NT上,Shell没有更新,会怎么样。当然,答案是属性页的Shell扩展。它与其它任何Shell扩展一样,都能在IE4.0上工作。
要实现哪些接口
&&&&&&&&&通过ATL COM AppWizard生成ATL代码之后,所需要解决的问题是:添加属性页到&属性&对话框需要实现哪些接口。事实上有两个接口:IShellPropSheetExt和IShellExtInit。头一个提供添加页的方法,而后一个仔细的初始化和建立Shell与扩展之间的连接。两者都在shlobj.h中定义。
&&&&&&&&&IShellPropSheetExt请求使用API函数建立新的属性页,这涉及到通用控件,而后这个页通过回调函数传递给Shell。也就是说,当调用IShellPropSheetExt方法时,Shell传递了一个指向函数的指针,这个函数由扩展回调,将页面作为变量。这个接口有两个方法,其中一个在绝大多数场合都不需要实现。
&&&&&&&&&单一方法的IShellExtInit接收在Shell中选中的文件(或文件组)的名字,并使它成为可用的模块。可以使用任何技术来存储这些名字,而典型的是使用成员变量。Shell扩展的初始化是一个过程,可能对不同类型的扩展有相当的变化,所以使这个机理通用是关键所在。
Shell扩展的初始化
&&&&&&&&&我们需要花费一点时间来讨论Shell扩展怎样初始化的问题。在这里&初始化&意指探测器调用扩展,传递正确的变量所遵循的过程。基本上,初始化可以取三种形式之一:不必初始化,经由IShellExtInit初始化,和经由IPersistFile初始化。初始化使用的方法依赖于Shell扩展本身的本质。
&&&&&&&&&下表给出各种类型扩展获得初始化的方法(参考前面的Shell扩展类型表)。
无须初始化
文件钩子,剪裁板
Shell扩展不要求任何初始化过程
经IShellExtInit初始化
关联菜单,属性页,
Shell扩展操作所有选中的文件。它们的名字以相同于拷贝到剪裁板的格式传递
经IPersistFile初始化
左键拖拽,图标
Shell扩展在文件上操作,无论其是否被选中,名字以Unicode串形式传递
启动Shell扩展的过程由调用一个或多个初始化接口的方法组成。当探测器感觉到它可能要触发Shell扩展的事件时,它知道注册了哪一种扩展,以及怎样初始化它。它所要做的全部工作就是附加对适当接口的查询操作。
&&&&&&&&&我们的目的是要详细描述当Shell扩展需要时IShellExtInit和IPersistFile接口的工作过程,因此,现在让我们看一下唤醒属性页Shell扩展时IShellExtInit接口的工作过程(我们也将在IconHandler扩展中讨论IPersistFile的初始化过程)。
IShellExtInit接口
&&&&&&&&&我们这里所涉及到的属性页扩展是通过IShellExtInit接口的方式装入的,它只有一个方法称为Initialize(),探测器唤醒并传递三个参数:
LPCITEMIDLIST
pidlFolder
对于属性页扩展总是NULL
LPDATAOBJECT
指向IDataObject对象的指针,可以用这个对象获得当前选中的文件
hkeyProgID
所涉及文件的注册表键
因为同一个接口服务于几种类型的扩展,头一个和第三个参数可以有不同的意义,这依赖于被初始化的类型。对于属性页,不涉及到文件夹,所以pidlFolder变量没有使用。hkeyProdID参数是HKEY Handle,指向注册表键,包含对象要唤醒的文件信息。例如,如果Shell扩展操作WMF文件,考虑上一章的例子,则hkeyProdID将握有:
HKEY_CLASSES_ROOT
/WinMetafile
对于属性页的扩展最重要的变量是lpdobj,它包含了指向实现IDataObject接口对象的指针。这是一个已知的接口,有许多用户接口都使用这个接口。基本上,IDataObject定义了运行模块之间要交换的数据块的行为,因此剪裁板和拖拽操作是它的主要应用领域。
&&&&拷贝数据到剪裁板和从剪裁板取得数据这种OLE方法说明了存储和恢复指向实现IDataObject对象指针的情况。同样,当你使用COM接口拖拽数据时,源和目的数据交换也是通过IDataObject完成的。另一个观察IDataObject对象的方法是:把IDataObject对象作为Windows Handle的演化&&即,表示包含数据的内存块的通用对象。这种增强提供了对数据的存储能力:
&&&&&&&&具有精确格式的数据,不只是通用的&某些东西的指针&
&&&&&&&&在存储介质中而不是在内存中的数据
&&&&&&&&同时容纳更多的数据块
IDataObject接口输出方法来取得和枚举数据。特别,它使用象FORMATETC和STGMEDIUM这样的结构来定义格式和数据存储介质。在获得IDataObject指针后,你可以询问它以便发现它是否在一定介质上包含特定格式的数据。过一会,在我们揭示了它怎样应用于属性页扩展之后,这一点就更清楚了。
&&&&回到属性页的Shell扩展。此时,传递给Initialize()的IDataObject对象包含一个HDROP Handle。在第6章我们看到,这个Handle包含了一个文件名列表,我们可以使用象DragQueryFile()这样的函数遍历这个列表。对于属性页扩展,这个列表包含在Shell中所有当前选中文件的名字。
&&&&属性页对话框仅在从Shell右击一个或多个选中文件并且从导出的关联菜单中选择属性项后弹出。选中的文件列表经由实现IDataObject的对象传递给Shell扩展,而且包含了CF_HDROP格式的数据。CF_HDROP是标准剪裁板格式之一,这种形式的数据存储在称之为HDROP的全程内存Handle上。
FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = lpdobj-&GetData(&fe, &medium);
if(SUCCEEDED(hr))
hDrop = static_cast&HDROP&(medium.hGlobal);
上面代码段说明怎样从IDataObject指针恢复HDROP Handle。GetData()通过FORMATETC变量接收要恢复的数据描述,如果成功,则经由STGMEDIUM变量返回。FORMATETC结构定义如下:
typedef struct tagFORMATETC
{&&&CLIPFORMAT cfF
DVTARGETDEVICE*
} FORMATETC, *LPFORMATETC;
就我们的观点,值得注意的成员是cfFormat和tymed,它们分别说明数据格式和存储介质类型。因而代码中CF_HDROP是数据格式,而TYMED_HGLOBAL表示全程内存Handle作为数据返回的存储介质。其它可能的存储介质是磁盘文件,原文件和指向IStorage或IStream对象的指针。
&&&&&&&&&下面我们给出实现&Do_nothing&的ATL类,其函数在建立示例工程(project)时将重载,下面清单是IShellExtInitImpl.h头文件,它包含大多数IShellExtInit接口的基本实现。
// IShellExtInitImpl.h
#include &AtlCom.h&
#include &ShlObj.h&
class ATL_NO_VTABLE IShellExtInitImpl : public IShellExtInit
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) = 0;
_ATL_DEBUG_ADDREF_RELEASE_IMPL(IShellExtInitImpl)
// IShellExtInit
STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY)
return S_FALSE;
IShellPropSheetExt接口
&&&&&&&&&提供添加新属性页方法的接口是IShellPropSheetExt,它输出两个函数(在IUnknown之上的函数):AddPages()和ReplacePage()。第一个函数有下面形式的参数:
LPFNADDPROPSHEETPAGE
lpfnAddPage
指向实际添加页面函数的指针
必须传递给由lpfnAddPage指定的函数的变量
AddPages()建立新的属性页,并调用从lpfnAddPage参数接收的函数。这是一个由Shell定义的回调函数,它有下面的原型:
BOOL CALLBACK AddPropSheetPageProc(HPROPSHEETPAGE hpage, LPARAM lParam);
第二个变量总是由Shell传递来,使第一个参数获得AddPages()的任务。对每一个注册属性页的Shell扩展,这个回调函数都被调用一次,特别是Shell正在显示属性对话框时。AddPages()函数可以添加一个或多个页面,然而,在加多个页面时,它必须建立页面并重复调用由lpfnAddPage指向的函数。
&&&&&&&&&另一个由IShellPropSheetExt输出的方法,ReplacePage(),仅仅用于置换控制面板小程序的属性页在我们的示例中没有实现这个函数,但它的原型是:
HRESULT ReplacePage(UINT uPageID, //&要置换的页索引
LPFNADDPROPSHEETPAGE lpfnReplacePage, //&指向置换页函数的指针
LPARAM lParam); //&附加到函数的变量
遵守我们早期的承诺,下面的清单是IShellPropSheetExtImpl.h,包含了IShellPropSheetExt接口的基本实现:
// IShellPropSheetExtImpl.h
#include &AtlCom.h&
#include &ShlObj.h&
class ATL_NO_VTABLE IShellPropSheetExtImpl : public IShellPropSheetExt
TCHAR m_szFile[MAX_PATH];
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) = 0;
_ATL_DEBUG_ADDREF_RELEASE_IMPL(IShellPropSheetExtImpl)
// IShellPropSheetExt
STDMETHOD(AddPages)(LPFNADDPROPSHEETPAGE, LPARAM)
return S_FALSE;
STDMETHOD(ReplacePage)(UINT, LPFNADDPROPSHEETPAGE, LPARAM)
return E_NOTIMPL;
添加新的属性页
&&&&&&&&&为了适当地开始一个工程(project),我们建立一个新的ATL DLL工程(project)WMFProp,并添加一个简单的对象PropPage。在ATL&部件框架生成以后,我们需要对新对象的头文件做一些改变,PropPage.h:
// PropPage.h :&声明&CPropPage&对象类
#ifndef __PROPPAGE_H_
#define __PROPPAGE_H_
#include "resource.h" //&主程序符号
#include &comdef.h& //&标准接口&GUIDs
#include "IShellExtInitImpl.h" // IShellExtInit
#include "IShellPropSheetExtImpl.h" // IShellPropSheetExt
BOOL CALLBACK PropPage_DlgProc(HWND, UINT, WPARAM, LPARAM);
////////////////////////////////////////////////////////////////////////////
// CPropPage
class ATL_NO_VTABLE CPropPage :
public CComObjectRootEx&CComSingleThreadModel&,
public CComCoClass&CPropPage, &CLSID_PropPage&,
public IShellExtInitImpl,
public IShellPropSheetExtImpl,
public IDispatchImpl&IPropPage, &IID_IPropPage, &LIBID_WMFPROPLib&
CPropPage()
DECLARE_REGISTRY_RESOURCEID(IDR_PROPPAGE)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CPropPage)
COM_INTERFACE_ENTRY(IPropPage)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IShellPropSheetExt)
END_COM_MAP()
// IPropPage
STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
STDMETHOD(AddPages)(LPFNADDPROPSHEETPAGE, LPARAM);
#endif //__PROPPAGE_H_
需要实现的接口方法是Initialize()和AddPages()。我们还声明了静态成员函数PropPage_DlgProc(),它用于定义被添加页面的行为&&这是新页面的窗口过程。
Initialize()函数的代码
&&&&&&&&&Initialize()方法代码如下:
HRESULT CPropPage::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT
lpdobj, HKEY hKeyProgID)
if(lpdobj == NULL)
return E_INVALIDARG;
//&初始化通用控件(属性页是通用控件)
InitCommonControls();
//&从IDataObject获得选中文件名,数据以CF_HDROP格式存储
FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = lpdobj-&GetData(&fe, &medium);
if(FAILED(hr))
return E_INVALIDARG;
HDROP hDrop = static_cast&HDROP&(medium.hGlobal);
if(DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0) == 1)
DragQueryFile(hDrop, 0, m_szFile, sizeof(m_szFile));
hr = NOERROR;
hr = E_INVALIDARG;
ReleaseStgMedium(&medium);
由于属性页是通用控件,我们需要初始化适当的库。这也说明必须#include commctrl.h,和引入comctl32.lib库。在使用前面描述的技术获得选中文件后,检查有多少选中文件。为简单起见,如果有多个选中文件,我们退出这个函数,这就是下面代码所做的操作:
if(DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0) == 1)
如上调用DragQueryFile()之后,返回选中文件数量。下一行则抽取第一个也是唯一一个文件(它的索引为0),并把它的名字存入m_szFile缓冲:
DragQueryFile(hDrop, 0, m_szFile, sizeof(m_szFile));
最后,所有活动完成后,通过调用ReleaseStgMedium()释放存储介质结构。
AddPages()函数的代码
&&&&AddPages()函数的代码如下:
HRESULT CPropPage::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
lstrcpy(g_szFile, m_szFile);
//&建立新页面需要填充PROPSHEETPAGE&结构
PROPSHEETPAGE
ZeroMemory(&psp, sizeof(PROPSHEETPAGE));
psp.dwSize = sizeof(PROPSHEETPAGE);
psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_DEFAULT;
psp.hInstance = _Module.GetModuleInstance();
psp.pszTemplate = MAKEINTRESOURCE(IDD_WMFPROP);
psp.pszTitle = __TEXT("预览");
psp.pfnDlgProc = PropPage_DlgP
psp.lParam = reinterpret_cast&LPARAM&(g_szFile); //&为dlgproc定制数据
psp.pcRefParent = reinterpret_cast&UINT*&(&_Module.m_nLockCnt);
//&建立新页面
HPROPSHEETPAGE hPage = ::CreatePropertySheetPage(&psp);
//&添加页面到属性页
if(hPage != NULL)
if(!lpfnAddPage(hPage, lParam))
::DestroyPropertySheetPage(hPage);
return NOERROR;
return E_INVALIDARG;
新页面包含一个对话框,既没有标题也没有边框,而且在上面代码中,PROPSHEETPAGE结构的pszTemplate成员被设置为它的ID。我们设计的对话框包含单个图像控件,具有SS_ENHMETAFILE风格,取名为IDC_METAFILE,附加一个对话框模板到工程的资源中对属性页面的Shell扩展总是必要的。然而,对话框要求对话框过程处理所有它包含的控件。在上例中是PropPage_DlgProc()简单地响应WM_INITDIALOG和绘制原文件,为此,我们使用在前一章中定义的函数。由于对话框过程不能访问类成员,我们通过PROPSHEETPAGE结构的lParam字段传递要显示的文件名,并且对话框过程接收指向这个结构的指针作为WM_INITDIALOG消息的lParam变量。
BOOL CALLBACK PropPage_DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
switch(uiMsg)
case WM_INITDIALOG:
HWND hwndMeta = GetDlgItem(hwnd, IDC_METAFILE);
LPPROPSHEETPAGE lppsp = reinterpret_cast&LPPROPSHEETPAGE&(lParam);
DisplayMetaFile(hwndMeta, reinterpret_cast&LPTSTR&(lppsp-&lParam));
return FALSE;
return FALSE;
注册Shell扩展
&&&&&&&&&我们前面说过,如果没有正确地注册Shell扩展,它们将不能工作:探测器不能找到要加载的模块。每一个Shell扩展每次关联到指定的文件对象是通过文件类型(比如说EMF),或通用的对象(如文件夹)。因而,在注册Shell扩展时,你必须考虑是否增加安装文件类型的信息。如果你写的Shell扩展是对系统文件类型的比如BMP,TXT,文件夹或&*,就不必注册新文件类型了。然而对于客户的文件类型(比如说XYZ),或没有默认定义的文件类型(就象EMF和WMF),你应该保证注册信息的输入。假定文件类型的注册信息正确地注册了,我们仍然需要添加几行到由ATL应用大师产生的标准注册脚本中。这些行应该与Shell扩展操作的文件类型或一同工作的文件类型相关。此时Shell扩展不仅必须注册连接WMF和EMF,还要在下面这些键下注册:
HKEY_CLASSES_ROOT
/WinMetafile
对应WMFs,&和
HKEY_CLASSES_ROOT
/EnhMetafile
对应EMFs。
Shell扩展必须在指定文件类键的shellex子键下注册,在shellex下,你需要建立附加的键分组各种类型的扩展,而且这些都有特定的名字。注册属性页Shell扩展的键为PropertySheetHandlers,在其下可以列出对这个文件类所有属性页Shell扩展的CLSID。
&&&&&&&&&有点陌生的是Shell扩展类型允许定义同一个文件类的多个服务器,它们被顺序调用。例如,很可能是有三个COM服务器实现位图文件类型的三个关联菜单的不同扩展。对于所有Shell扩展,除了那些处理剪裁板和左键拖拽的扩展,都允许有多重扩展存在。后面我们还要讨论这个问题。
&&&&&&&&&下面清单说明怎样将默认的注册脚本改变为正确注册属性页Shell扩展的脚本。
WMFProp.PropPage.1 = s 'PropPage Class'
CLSID = s '{0D0ED2-8CDB-00}'
WMFProp.PropPage = s 'PropPage Class'
CLSID = s '{0D0ED2-8CDB-00}'
CurVer = s 'WMFProp.PropPage.1'
NoRemove CLSID
ForceRemove {0D0ED2-8CDB-00} = s 'PropPage Class'
ProgID = s 'WMFProp.PropPage.1'
VersionIndependentProgID = s 'WMFProp.PropPage'
ForceRemove 'Programmable'
InprocServer32 = s '%MODULE%'
val ThreadingModel = s 'Apartment'
'TypeLib' = s '{0D0E354B--8CDB-00}'
WinMetafile
PropertySheetHandlers
{0D0ED2-8CDB-00}
EnhMetafile
PropertySheetHandlers
{0D0ED2-8CDB-00}
下图说明了注册增强元文件后的注册表状态。注意,其中有三个属性页的Shell扩展。如果你还有另一个增强元文件的Shell扩展&&例如管理关联菜单&&它们应该以同样的方法注册,但是是在另一个子键下。定位在与PropertySheetHandlers同层。
现在Shell扩展正确地注册了以后,你就能右击EMF或WMF文件,并且有下面的行为出现:
测试Shell扩展
&&&&&&&&&到目前为止我们已经编写并注册了一个Shell扩展,现在我们来看一下它是否做了它应该做的工作。运行Shell扩展的唯一方法是启动探测器并执行引起Shell扩展动作的活动,但是要使探测器确信你的扩展存在可能是比较困难的。在一定场合下,你可能需要注销登录,甚至重启机器来使Shell加载更新版的扩展,相反,对比重启机器,简单地关闭探测器可能更好一点,而且可以使用任务条实用程序,我们在第9章中就是这么做的。还有就是按F5键,但这种方法不能总奏效。
参见这一章后面的Shell扩展开发者手册,其中有更详细的讨论
除了这些小困难之外,我们现在假设正在运行你的扩展。当你感觉到一个错误,并且需要排除代码找到错误发生点时复杂的事情发生了。排除Shell扩展的错误不是直觉的任务,我们需要仔细地检查扩展操作的过程。第一步是设置explorer.exe为排错会话的可执行程序。因为Shell扩展是DLL,并且不是独立可执行程序,因此这一步是必要的。注意,你需要指定探测器的全路径:
第二步是要保证你的Shell扩展工程在VC++IDE中打开。这个技巧是停止Shell,然后在排错器下导出它的新实例运行,这比想象的要困难一点。如果你简单地运行排错器,可以引起探测器窗口的出现,但是这并不是说新的Shell进程已经启动,对于要发生的排错,你首先需要终止Shell进程,而不终止机器上的其它进程,然后再次运行排错器,它将实际地建立一个可排错的Shell进程。
&&&&&&&&&要停止Shell,你可以编程发送WM_QUIT消息到唯一的窗口类&program&(我们在第9章中已经讨论了这个技术),要手动做这个工作,执行下面的操作:
从开始菜单中选择&关闭&,并且在按下Ctrl-Alt-Shift时点击&取消&。这并不容易做到,但是它能工作。当你这样做了之后,任务条消失,你将感觉到系统重启了,但是并没有导致机器的重启。没有任何错误发生,所有都在控制之中。
使用Alt-Tab键导出VC++窗口到顶部,然后运行排错器,现在任务条将再次出现,它标志着新的Shell进程在VC++的排错器下运行。
现在所要做的是与任何其它程序排错一样:点击&Build |&启动排错&| Go&菜单项。当探测器窗口显示出来时,执行导出Shell扩展的活动。在这个例子中你应该选择WMF文件,右击,并打开属性对话框。
你放置在代码中的断点现在能象通常一样被感觉到,并且在遇到时引起过程停止。在完成排错之后双击桌面将导出任务管理器窗口来到前面:
选择&文件&|&运行&,导出探测器,所有事情都恢复到以前的状态。我们给出的并不是你每天都要操作的过程,但是它却是能够解决Shell扩展排错的问题。
&&&&&&&&&值得注意的是控制台小程序&&它们总是包含一系列帐单页面&&不是运行在探测器地址空间中的。也就是说你不能使用上面描述的技术对它们排错。相反,应该指定运行rundll32.exe作为排错会话的可执行程序。
在Windows NT下排错
&&&&&&&&&如果需要在NT下测试,我们建议在下面的注册键上添加你自己的值:
HKEY_CURRENT_USER
/Microsoft
/CurrentVersion
添加的值称为DesktopProcess,其类型为REG_DWORD,值为1。设置了这个值之后,重新登录,你将发现WindowsNT的Shell被划分成两个部分&&桌面,任务条和托盘域运行在文件夹和文件的不同进程中。现在在VC++&环境下运行探测器,你实际正在启动可以排错的新进程,而且任何冲突都不影响稳定的系统桌面。
卸载Shell扩展
&&&&&&&&&另一个关于Shell扩展测试的科目是确定什么时候卸载Shell扩展。与其它COM对象一样,Shell扩展是持续流目标,要求通过DllCanUnloadNow()导出卸载过程。模块是否可以被卸载依赖于它内部的引用计数。没有自动机理来从内存删除引用计数已经变为0的模块,因此探测器调用DllCanUnloadNow()越快,无用的Shell扩展卸载的就越快。注意,卸载后的Shell扩展模块是可以安全再编译的,这对于Shell扩展在开发期间是十分重要的。
&&&&&&&&&默认情况下,探测器每十秒钟尝试一次卸载Shell扩展。资料说明可以通过设置下面注册键的默认值为1来改变这个卸载尝试的频率:
HKEY_LOCAL_MACHINE
/Microsoft
/CurrentVersion
/AlwaysUnloadDll
设置这个键在较老的系统上并没有多大改变&&Shell扩展的卸载没有更快。
再说属性页的Shell扩展
&&&&&&&&&上面的例子仅在选种单个文件时才能工作,而且没有阻止我们为每个被选中文件添加属性页,例如:
这种改变要求的代码不是主要的,甚至可以用同时运行多个扩展来实现这个目的&&探测器将顺序管理它们。唯一的缺点是你可能需要附加某些属性页的拷贝。下面就看一下我们需要做哪些改变。
修改代码来支持多重选择
&&&&&&&&&要做的头一件也是最显然的一件事就是Shell扩展的类声明,以使其反映出我们不再使用单文件保持轨迹,而是使用列表文件名。这个列表有一个上限,因为prsht.h(属性页头文件)限制其任何一个页表上的页数最大到100,助记常量为MAXPROPPAGES。
&&&&&&&&&这说明在一个页表控件上不可能管理超过100的页面数&&我们已经注意到这个控件不能有超过六行的页面,因此合理的最大数是30&35页。下面是我们的新版本IShellPropSheetExt.h:
// IShellPropSheetExtImpl.h (多选版本)
//////////////////////////////////////////////////////////////////////
#include &AtlCom.h&
#include &ShlObj.h&
class ATL_NO_VTABLE IShellPropSheetExtImpl : public IShellPropSheetExt
TCHAR m_aFiles[MAXPROPPAGES][MAX_PATH];
int m_iNumOfF
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) = 0;
_ATL_DEBUG_ADDREF_RELEASE_IMPL(IShellPropSheetExtImpl)
// IShellPropSheetExt
STDMETHOD(AddPages)(LPFNADDPROPSHEETPAGE, LPARAM)
return S_FALSE;
STDMETHOD(ReplacePage)(UINT, LPFNADDPROPSHEETPAGE, LPARAM)
return E_NOTIMPL;
在Initialize()和AddPages()的实现中代码也要做稍微的改变。下面是新的Initialize():
HRESULT CPropPage::Initialize(LPCITEMIDLIST pidlFolder,
LPDATAOBJECT lpdobj, HKEY hKeyProgID)
if(lpdobj == NULL)
return E_INVALIDARG;
//&初始化通用控件
InitCommonControls();
//&获取CF_HDROP格式数据
FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = lpdobj-&GetData(&fe, &medium);
if(FAILED(hr))
return E_INVALIDARG;
HDROP hDrop = static_cast&HDROP&(medium.hGlobal);
//&取得选中文件数
m_iNumOfFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
//&规格化到允许的最大数
m_iNumOfFiles = (m_iNumOfFiles &= MAXPROPPAGES ? MAXPROPPAGES : m_iNumOfFiles);
//&抽取和管理所有选中的文件
for(int i = 0 ; i & m_iNumOfF i++)
DragQueryFile(hDrop, i, m_aFiles[i], MAX_PATH);
Rele〉&0aseStgMedium(&medium);
现在所有文件都存储在文件名数组中了。它们将在AddPages()中一次处理:
HRESULT CPropPage::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
for(int i = 0 ; i & m_iNumOfF i++)
//&检查选中的文件是否为元文件
LPTSTR p = PathFindExtension(m_aFiles[i]);
if(lstrcmpi(p, __TEXT(".WMF")) && lstrcmpi(p, __TEXT(".EMF")))
//&分配要传递的串。它将在&dlgproc&中被释放。
LPTSTR psz = new TCHAR[MAX_PATH];
lstrcpy(psz, m_aFiles[i]);
//&剥离路径和扩展名,以显示在标题上
LPTSTR pszTitle = PathFindFileName(m_aFiles[i]);
PathRemoveExtension(pszTitle);
//&填写PROPSHEETPAGE结构
PROPSHEETPAGE
ZeroMemory(&psp, sizeof(PROPSHEETPAGE));
psp.dwSize = sizeof(PROPSHEETPAGE);
psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE | PSP_DEFAULT;
psp.hInstance = _Module.GetModuleInstance();
psp.pszTemplate = MAKEINTRESOURCE(IDD_WMFPROP);
psp.pszTitle = pszT
psp.pfnDlgProc = PropPage_DlgP
psp.lParam = reinterpret_cast&LPARAM&(psz);
psp.pcRefParent = reinterpret_cast&UINT*&(&_Module.m_nLockCnt);
HPROPSHEETPAGE hPage = ::CreatePropertySheetPage(&psp);
//&添加页面到属性页上
if(hPage != NULL)
if(!lpfnAddPage(hPage, lParam))
:: DestroyPropertySheetPage(hPage);
return NOERROR;
关于这个版本的AddPages()函数,有几点需要注意,首先,我们设置属性页的标题为没有路径和扩展名的文件名,这使用了一些来自shlwapi.dll的函数,因此#include &shlwapi.h&&和连接shlwapi.lib是必须的。第二,在清单中注释了引用要在对话框过程中删除的指针,这个指针是在循环中分配的,所以现在的PropPage_DlgProc()应该是:
BOOL CALLBACK PropPage_DlgProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
switch(uiMsg)
case WM_INITDIALOG:
HWND hwndMeta = GetDlgItem(hwnd, IDC_METAFILE);
LPPROPSHEETPAGE lppsp = reinterpret_cast&LPPROPSHEETPAGE&(lParam);
DisplayMetaFile(hwndMeta, reinterpret_cast&LPTSTR&(lppsp-&lParam));
delete [] reinterpret_cast&LPTSTR&(lppsp-&lParam);
return FALSE;
return FALSE;
最后,函数现在能够识别WMF/EMF与其它文件类型&&它接收前者而拒绝后者。当选中了一定数量的文件时,你不用保证它们都是同样类型的。这就是说在右击给定的属性对话框时,你并不需要选择期望类型的文件,因此也不能保证你的扩展被使用。例如,选择EMF和BMP文件,在选中的BMP上右击请求属性对话框时,你将获得BMP的对话框,相反,如果你的文件是元文件或在元文件上右击,你所获得的是下面的情形:
&&&&&&&&&关于添加新项到关联菜单,Shell扩展是最灵活的技术,因为它们给出了事件的全部控制。在前一章中,我们探讨了使用注册表操作来达到同样目的的方法,但是那种技术引起外部代码段执行。用Shell扩展,你可以运行直接与Shell通讯的代码段,接收和返回信息。如果你编写和注册了关联菜单的Shell扩展,你可以有机会选择指定菜单项串,状态条描述和每次菜单被显示的行为。只要你喜欢,总是能编程改变它们,而不需要修改任何注册表。
实现IContextMenu接口
&&&&&&&&&处理关联菜单的Shell扩展就是编写一个实现IContextMenu接口的COM服务器。除了这个变化外,在我们前面描述的示例中不需要做任何改动。从IUnknown导出的IContextMenu有三个函数:
GetCommandString()
InvokeCommand()
QueryContextMenu()
它们分别恢复菜单项的描述,响应点击操作和添加新命令到菜单。
新项的帮助文字
&&&&&&&&&GetCommandString()有下面的原型:
HRESULT GetCommandString(UINT idCmd, //&需要描述的菜单命令ID
UINT uFlags, //&指定要做什么的标志
UINT* pwReserved, //&保留,总是NULL
LPSTR pszName, //&接收要恢复串的缓冲(最大&40)
UINT cchMax); //接收串的实际长度
GetCommandString()函数的uFlags可用的取值是:
GCS_HELPTEXT
Shell要求项的描述串
GCS_VALIDATE
Shell简单地想要知道是否具有这个ID的项存在和有效
Shell要求这个菜单项动词的语言无关的名
动词是实施命令的名字(我们在前面章节中已经解释过了,特别在第8章)。动词可通过ShellExecute()和ShellExecuteEx()函数执行。在通过注册表静态添加新的菜单项时,建立的键名就是语言无关的动词,其后的命令则隐藏在&Command&子键下。在动态添加菜单项时,你应该实现InvokeCommand()来提供类似&Command&键的行为,并且适当地响应GCS_VERB标志令Shell知道新命令的动词。
&&&&注意,你传递的任何帮助文字都将在40字符之后返回,尽管传递了较长的串,也不要截断除了串本身之外的任何东西。
新项的行为
&&&&&&&&&InvokeCommand()是在用户点击关联菜单项时被调用的方法。其原型为:
HRESULT InvokeCommand(LPCMINVOKECOMMANDINFO lpici);
CMINVOKECOMMANDINFO结构声明如下:
typedef struct _CMINVOKECOMMANDINFO
LPCSTR lpV
LPCSTR lpP
LPCSTR lpD
DWORD dwHotK
} CMINVOKECOMMANDINFO, *LPCMINVOKECOMMANDINFO;
让我们更详细地讨论这个结构:
这个结构的尺寸
允许dwHotkey和hIcon成员,和防止任何UI活动的屏蔽位,就象消息框的标志一样。
菜单的父窗口
一个命令ID给出的DWORD类型值(高字为0),或表示要执行动词的串
lpParameters
如果接口从Shell调用,总是NULL
lpDirectory
如果接口从Shell调用,总是NULL
如果启动新应用,这是一个传递给ShowWindow()的&SW_&型常量。
由命令分配给应用启动的热键。如果fMask关闭了它的特定位,这个热键不必考虑。
由命令分配给启动应用的图标,如果fMask关闭了它的特定位,这个图标不必考虑。
fMask的合法值如下:
CMIC_MASK_HOTKEY
dwHotKey成员是可用的
CMIC_MASK_ICON
hIcon成员是可用的
CMIC_MASK_FLAG_NO_UI
没有可以影响用户界面的活动发生(例如,建立窗口或消息框)
lpVerb成员是一个32位值,有两种方法确定其内容,它可以是调用
lpVerb = MAKEINTRESOURCE(idCmd, 0);
的结果。这里idCmd是菜单项的ID,而lpVerb也可以表示要执行动词的名字。此时,高字不为0,这个值实际指向一个串。
与其它Shell相关的接口类似,IContextMenu也可以从Shell之外调用,不用响应在Shell元素上的UI活动。例如,当你获得了IShellFolder指针后,就可以请求绑定在这个文件夹或文件对象上的IContextMenu接口。然后就可以使用IContextMenu编程唤醒动词,而不需要通过Shell。此时的lpParameters和lpDirectory可能不是NULL。
&&&&&&&&&此外,你还可以使用ShellExecuteEx()来调用Shell扩展动态添加的动词。此时可以通过这个接口函数指定附加的参数和工作目录,这就是最终所填写的lpParameters和lpDirectory变量。(参见第8章)
&&&&&&&&&在建立给定文件对象的关联菜单时,Shell通过调用QueryContextMenu()查询所有注册的关联菜单Shell扩展来添加扩展所拥有的项。这个函数的原型是:
HRESULT QueryContextMenu(HMENU hmenu, //&要添加项的菜单Handle
UINT indexMenu, //&被添加的第一项的索引(从0开始)
UINT idCmdFirst, //&新项的最低可用命令ID
UINT idCmdLast, //&新项的最高可用命令ID
UINT uFlags); //&影响关联菜单的属性
在添加新菜单项时,Shell指示头一个添加项的位置,以及命令ID的取值范围。下面一小段代码显示了典型的通过QueryContextMenu()插入新项的方法:
idCmd = idCmdF
lstrcpy(szItem, ...);
InsertMenu(hMenu, indexMenu++, MF_STRING | MF_BYPOSITION, idCmd++, szItem);
在所有uFlags变量可用的标志中,我们所困扰的是CMF_NORMAL和CMF_DEFAULTONLY。其它的对于&简单&的Shell扩展是没有意义的,而主要是应用于命名空间扩展。下面是这些值的完整列表:
CMF_CANRENAME
如果设置,命名空间扩展应该添加一个&重命名&项
CMF_DEFAULTONLY
用户双击,命名空间扩展可以添加它为默认项。Shell扩展不应该做任何事情,事实上如果这个标志设置,应该避免添加项。
CMF_EXPLORE
当探测器打开树窗口时设置此标志
CMF_INCLUDESTATIC
Shell扩展不顾此标志
CMF_NODEFAULT
菜单不应该有默认项,Shell扩展忽略这个标志,但命名空间扩展应该避免定义默认项
CMF_NORMAL
非特殊情况,Shell扩展可以添加它们的项。
CMF_NOVERBS
Shell扩展忽略这个标志。它用于&发送到&菜单。
CMF_VERBSONLY
Shell扩展忽略这个标志。它用于快捷方式对象的菜单
你肯定很奇怪Shell扩展为什么忽略在命名空间扩展中有用的标志,或忽略应用于特定菜单如&发送到&和快捷方式菜单的标志。IContextMenu不是一个Shell扩展接口吗?
&&&&&&&&&实际上,答案是否定的,IContextMenu是提供关联菜单功能的通用COM接口。几乎所有的系统菜单都可以通过在注册表的适当位置注册关联菜单处理器来扩展&&Shell加载它,因而提供添加和管理客户菜单项的可能性。IContextMenu可用于在探测器窗口以外工作,我们在后面将给出这方面的例子。命名空间扩展是一个定制的Shell观察,可以直接调用提供的关联菜单到用户,因此IContextMenu也影响命名空间扩展。
QueryContextMenu()的返回值
&&&&与其它COM&函数一样,QueryContextMenu()返回HRESULT值。在很多情况下,你可以使用预定义常量,偶尔,需要格式化特定的返回值。QueryContextMenu()就是需要这样做的函数之一。我们都知道HRESULT是32位值,其位被分成三部分:严格(severity),简易(facility)和代码(code)。QueryContextMenu()要求你返回代码到特定值,和0。特别是,你应该返回添加的菜单项数。要格式化HRESULT,MAKE_HRESULT()宏是极为有用的:
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, idCmd - idCmdList);
可执行程序的相关列表
&&&&&&&&&现在我们把学过的关于关联菜单的所有技术都串联在一起做一个练习。在操作探测器时你可能会遇到成百上千的可执行程序,是否有人能告诉你这些程序引用了什么库呢?程序有一个静态引用的模块列表,它称之为相关列表。
&&&&&&&&&通过扫描Win32&可执行程序的二进制格式(假设对Win32简携可执行格式有很好的理解),就有可能抽取出一个应用所需要的所有DLL名。在这个例子中,我们打算实现一个工具,作为关联菜单对EXE和DLL文件查看它们的相关列表。
&&&&&&&&&在开始之前,我们要说明几件事,首先,这个工具不需要运行应用&&这将限制对其检查字节。其次,它仅能恢复那些在代码中显式引入的DLL。这是因为仅静态连接到工程中的DLL在代码中留有标记,如果程序通过LoadLibrary()动态装入DLL,这个DLL不在引入表中引用,我们就不能跟踪它。
建立关联菜单的扩展
&&&&&&&&&我们并不打算就获取Win32可执行程序相关列表给出方方面面的细节说明,因为这是一个十分复杂的科目并且超出了本书的范围。如果你感兴趣,请参考相关的MSDN资料。在这个例子中,我们使用相对新的DLL,其名字为ImageHlp。这个库并不输出特殊的函数来获得文件名,而是通过使用其中的一个例程,来完成这些操作。
&&&&&&&&&开始,使用ATL COM应用大师建立DLL工程(project),取名为Depends,加入一个新的简单对象ExeMenu,接受所有默认的选项。这是一个实现关联菜单Shell扩展所要求接口的对象:IContextMenu和IShellExtInit。下面是我们需要对ExeMenu.h主头文件所作的改变:
#include "resource.h" //&主符号
#include "IContextMenuImpl.h" // IContextMenu
#include "IShellExtInitImpl.h" // IShellExtInit
#include "DepListView.h" //&对话框
#include &comdef.h& //&接口&IDs
//////////////////////////////////////////////////////////////////////////
// CCExeMenu
class ATL_NO_VTABLE CExeMenu :public CComObjectRootEx&CComSingleThreadModel&,
public CComCoClass&CExeMenu, &CLSID_CExeMenu&,
public IShellExtInitImpl,
public IContextMenuImpl,
public IDispatchImpl&IExeMenu, &IID_IExeMenu, &LIBID_DEPENDSLib&
CExeMenu()
TCHAR m_szFile[MAX_PATH]; //&可执行文件名
CDepListView m_D //&显示结果的对话框
// IContextMenu
STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT);
STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO);
STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT , UINT, UINT);
// IShellExtInit
STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
DECLARE_REGISTRY_RESOURCEID(IDR_EXEMENU)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CExeMenu)
COM_INTERFACE_ENTRY(IExeMenu)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()
// IExeMenu
CExeMenu类从IShellExtInitImpl和IContextMenuImpl两个ATL类中导出,它提供IShellExtInit&和&IContextMenu接口的基本实现。IShellExtInitImpl.h头文件与我们在前一个例子中使用的一样,而IContextMenuImpl.h头文件有如下形式:
// IContextMenuImpl.h
#include &AtlCom.h&
#include &ShlObj.h&
class ATL_NO_VTABLE IContextMenuImpl : public IContextMenu
TCHAR m_szFile[MAX_PATH];
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) = 0;
_ATL_DEBUG_ADDREF_RELEASE_IMPL(IContextMenuImpl)
// IContextMenu
STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT)
return S_FALSE;
STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO)
return S_FALSE;
STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT , UINT, UINT)
return S_FALSE;
退一步说,这里的是最小实现。在其它情况下,你可能需要准备更有效的类,并增强代码可重用的质量,然而,对于我们的例子,这段代码足够了。剩下的就是要提供两个接口全部函数的代码,它们都包含在ExeMenu.cpp中:
// QueryContextMenu
HRESULT CExeMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst,
UINT idCmdLast, UINT uFlags)
//&这个Shell扩展打算在EXE文件的关联菜单上提供相关列表
UINT idCmd = idCmdF
//&添加新菜单项
InsertMenu(hmenu, indexMenu++, MF_STRING | MF_BYPOSITION,idCmd++,
&__TEXT("Dependency &List"));
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, idCmd - idCmdFirst);
// InvokeCommand
HRESULT CExeMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
//&建立模式对话框显示信息
lstrcpy(m_Dlg.m_szFile, m_szFile);
m_Dlg.DoModal();
return S_OK;
//&取得命令串
HRESULT CExeMenu::GetCommandString(UINT idCmd, UINT uFlags, UINT* pwReserved,
LPSTR pszText, UINT cchMax)
//&我们不关心命令ID,因为我们只有单个项
if(uFlags & GCS_HELPTEXT)
lstrcpyn(pszText, __TEXT("显示模块需要的所有DLL"), cchMax);
return S_OK;
// Initialize
HRESULT CExeMenu::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj,
HKEY hKeyProgID)
if(lpdobj == NULL)
return E_INVALIDARG;
//&取得&CF_HDROP
FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = lpdobj-&GetData(&fe, &medium);
if(FAILED(hr))
return E_INVALIDARG;
//&取得选中文件名
DragQueryFile(reinterpret_cast&HDROP&(medium.hGlobal), 0, m_szFile, MAX_PATH);
ReleaseStgMedium(&medium);
应该看到,Initialize()代码与前面属性页例子中的初始化代码基本一致。
初始化关联菜单扩展
&&&&&&&&&前面我们说过,Initialize()的参数对不同类型的Shell扩展是不同的。对于关联菜单扩展,pidlFolder变量是文件夹的PIDL,它包含选中的文件对象。这些文件对象由lpdobj通过IDataObject接口指向,IDataObject接口我们在上一个例子中遇到过。hKeyProgID参数指定了选中文件对象的文件类,而且,如果选中了多个对象,它指向有焦点的一个。
获取可执行的关联链表
&&&&&&&&&这个扩展的目的是当用户点击&相关列表&菜单项时:
Shell将调用InvokeCommand()方法导出对话框。在这个截图中注意状态条中的显示文字,这是我们通过GetCommandString()函数提供的串。我们使用ATL对象大师添加一个对话框命名为DepListView,并且加入一个公共数据成员m_szFile来保存文件名:
enum {IDD = IDD_DEPLISTVIEW};
TCHAR m_szFile[MAX_PATH];
对话框的初始化在其OnInitDialog()方法中发生,这要求包含shlobj.h&和&windowsx.h到DepListView.h的顶部:
LRESULT CDepListView::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL& bHandled)
//&准备列表观察,使用前面章中定义的函数
HWND hwndList = GetDlgItem(IDC_LIST);
LPTSTR pszCols[] = {__TEXT("Library"), reinterpret_cast&TCHAR*&(280),
__TEXT("Version"), reinterpret_cast&TCHAR*&(103)};
MakeReportView(hwndList, pszCols, 2);
//&使用省略号设置文件名,如果它太长的话
TCHAR szTemp[60] = {0};
PathCompactPathEx(szTemp, m_szFile, 60, '//');
SetDlgItemText(IDC_FILENAME, szTemp);
//&获得引入表的尺寸
int iNumOfBytes = GetImportTableSize(m_szFile);
if(iNumOfBytes &= 0)
//&取得COM分配器&并保留一些内存
LPMALLOC pM = NULL;
SHGetMalloc(&pM);
LPTSTR psz = static_cast&LPTSTR&(pM-&Alloc(iNumOfBytes));
if(psz == NULL)
::MessageBox(0, __TEXT("没有足够的内存!"), 0, MB_ICONSTOP);
pM-&Release();
ZeroMemory(psz, iNumOfBytes);
//&访问引入表
int iNumOfLibs = GetImportTable(m_szFile, psz);
if(iNumOfLibs &= 0)
pM-&Release();
int i = 0;
while(i & iNumOfLibs)
// p&为列表观察格式化NULL分割的串
TCHAR buf[2048] = {0};
LPTSTR p =
lstrcpy(p, psz);
lstrcat(p, __TEXT("/0"));
p += lstrlen(p) + 1;
//&取得版本信息
TCHAR szInfo[30] = {0};
SHGetVersionOfFile(psz, szInfo, NULL, 0);
lstrcpy(p, szInfo);
lstrcat(p, __TEXT("/0"));
p += lstrlen(p) + 1;
//&添加传到列表观察
AddStringToReportView(hwndList, buf, 2);
//&下一个库
psz += lstrlen(psz) + 1;
pM-&Release();
首先我们通过添加两个列来格式化报告列表观察,一为文件名,一为版本号。第二,我们读出执行模块的引入表,并格式化一个NULL分隔的串。为了处理这个对话框,我们重用了一些函数&&MakeReportView()和AddStringToReportView(),以及SHGetVersionOfFile()函数。下图显示了最后的对话框:
这个对话框由表示为IDC_LIST的报告列表和命名为IDC_FILENAME的文字标签组成。还要注意,我们使用了shlwapi.dll中的PathCompactPathEx()函数来强迫文件名到固定的字符数&&当文件名太长时自动插入省略号来截断它。
&&&&&&&&&我们前面说过不打算深入讨论获取相关列表的技术,但是这个过程有几件事是需要提到的。ImageHlp API是在Windows9x和NT下可用的,它提供在可执行模块产生的内存映像上操作的函数。还有些函数遍历符号表把它映射进内存。(参见MSDN资料库)。
&&&&特别值得注意的函数是BindImageEx(),它允许你获取可执行模块从外部库引入的任何函数的虚地址。从我们的观点看,这个函数接受一个回调例程,并且传递每一个它遇到的DLL名到这个例程。通过钩住这些调用,我们能够很容易地计算出需要多少字节来存储整个名字列表(GetImportTableSize()),并且把所有名字都变成NULL分隔的串(GetImportTable())。
&&&&我们打算用一个简单的DLL来提供这些函数,头文件为DepList.h,应该在顶部包含#include&&DepListView.h:
#include &windows.h&
#include &imagehlp.h&
//&返回指定名DLL的字节数
int APIENTRY GetImportTableSize(LPCTSTR pszFileName);
//&用DLL名充填指定的缓冲
int APIENTRY GetImportTable(LPCTSTR pszFileName, LPTSTR pszBuf);
较大的源代码是DepList.cpp:
#pragma comment(lib, "imagehlp.lib")
#include "DepList.h"
/*----------------------------------------------------------------*/
// GLOBAL&节
/*----------------------------------------------------------------*/
LPTSTR* g_ppszBuf = NULL;
int g_iNumOfBytes = 0;
int g_iNumOfDLLs = 0;
BOOL CALLBACK SizeOfDLLs(IMAGEHLP_STATUS_REASON, LPSTR, LPSTR, ULONG, ULONG);
BOOL CALLBACK GetDLLs(IMAGEHLP_STATUS_REASON, LPSTR, LPSTR, ULONG, ULONG);
/*----------------------------------------------------------------*/
//&过程:GetImportTableSize()
/*----------------------------------------------------------------*/
int APIENTRY GetImportTableSize(LPCTSTR pszFileName)
g_iNumOfBytes = 0;
//&绑定到可执行
BindImageEx(BIND_NO_BOUND_IMPORTS | BIND_NO_UPDATE,
const_cast&LPTSTR&(pszFileName), NULL, NULL, SizeOfDLLs);
return g_iNumOfB
BindImageEx()的原型是:
BOOL BindImageEx(DWORD dwFlags,
LPSTR pszFileName,
LPSTR pszFilePath,
LPSTR pszSymbolPath,
PIMAGEHLP_STATUS_ROUTINE pfnStatusProc);
你必须在pszFileName中指定要操作的文件名,并且可能包含路径。如果不包含路径,可以使用pszFilePath来指定搜索pszFileName的根路径。更重要的是,这个函数回调pfnStatusProc中的例程,这个例程在函数绑定到指定可执行模块期间被唤醒,下面是回调的原型:
BOOL CALLBACK BindStatusProc(IMAGEHLP_STATUS_REASON Reason,
LPSTR ImageName,
LPSTR DllName,
ULONG Parameter);
我们唯一感兴趣的参数是Reason&和&DllName。第二个参数的目的是显然的,而第一个参数令你过滤对这个函数的众多调用,使之专注于实际感兴趣的。我们仅想知道需要多少字节来存储所有模块的引用,以及它们是哪些模块。SizeOfDLLs()是返回文件引入表尺寸的回调函数,GetDLLs()是通过调用BindImageEx()连接到所有绑定模块名而获得返回NULL分隔串的函数。这个串与版本信息组合产生输出显示。
/*----------------------------------------------------------------*/
//&过程: GetImportTable
/*----------------------------------------------------------------*/
int APIENTRY GetImportTable(LPCTSTR pszFileName, LPTSTR pszBuf)
g_ppszBuf = &pszB
g_iNumOfDLLs = 0;
//&绑定到可执行
BindImageEx(BIND_NO_BOUND_IMPORTS | BIND_NO_UPDATE,
const_cast&LPTSTR&(pszFileName), NULL, NULL, GetDLLs);
return g_iNumOfDLLs;
/*----------------------------------------------------------------*/
//&过程: SizeOfDLLs()
// Description.:&计算DLL尺寸的回调
/*----------------------------------------------------------------*/
BOOL CALLBACK SizeOfDLLs(IMAGEHLP_STATUS_REASON Reason,
LPSTR ImageName, LPSTR DllName, ULONG Va, ULONG Parameter)
if(Reason == BindImportModule || Reason == BindImportModuleFailed)
g_iNumOfBytes += lstrlen(DllName) + 1;
return TRUE;
/*----------------------------------------------------------------*/
//&过程: GetDLLs()
// Description.:&封装串的回调
/*----------------------------------------------------------------*/
BOOL CALLBACK GetDLLs(IMAGEHLP_STATUS_REASON Reason, LPSTR ImageName,
LPSTR DllName, ULONG Va, ULONG Parameter)
if(Reason == BindImportModule || Reason == BindImportModuleFailed)
lstrcpy(*g_ppszBuf, DllName);
*g_ppszBuf += lstrlen(*g_ppszBuf) + 1;
g_iNumOfDLLs++;
return TRUE;
最后,这些函数由&DepList.def&文件输出:
GetImportTableSize @1
GetImportTable @2
现在,你可以编译我们给出的所有代码了。
&&&&&&&&&这个清单显示了需要添加到ATL脚本ExeMenu.rgs末尾的修改代码,以便注册我们的Shell扩展。
ContextMenuHandlers
{F-11D2-9DAF-A}
ContextMenuHandlers
{F-11D2-9DAF-A}
改变之后,在下一次启动Shell时,你将发现由右键在EXE和DLL文件上生成的关联菜单有一个新的&相关列表&项。这是我们的Shell扩展给出的。
添加新查找菜单
&&&&&&&&&产生关联菜单扩展的另一个值得注意的用途是定制显示&查找&菜单的列表,例如,我们可以添加查找所有当前运行中进程的工具。
倘若我们已经有了一个添加了新的&查找&实用程序的关联菜单,则要做的只是写几个注册表信息段:
在你所看到的静态键下,需要添加新键FindProcess,并使之成为根的新子键。这个键的默认值必须是一个关联菜单扩展的CLSID。在它的下面,键名为0&的默认值是显示在菜单上的串。最后通过添加0键的DefaultIcon子键,可以为这个菜单项分配图标。
&&&&稍微思考一下,我们将看到这是一个陌生的而且是最小的Shell扩展。不需要任何初始化,因为没有要操作的文件。不需要描述,因为没有状态条,甚至不需要显式添加新项,因为Shell在读注册表时作了这个工作。事实上我们需要Shell扩展来定制&查找&菜单一点也不神秘。
&&&&&&&&&因为&查找&菜单也是通过探测器导出的,你可能以为描述是必须的,但是经过快速测试已经存在的菜单项后,我们知道,不是这样。建立关联菜单Shell扩展的复杂性减少到仅仅实现InvokeCommand()方法,这是一个导出实际运行查找实用程序的函数。
设置注册表
&&&&&&&&&编写关联菜

我要回帖

更多关于 微信继续发送此红包 的文章

 

随机推荐