
| 出版日期:1998-10-26 总期号:771 本年期号:81 |
|
深入windows内部探险记
马飞涛 四通利方和金山词霸的用户都曾见识过屏幕抓字技术,鼠标指哪就翻译哪个单词,这个技术在windows系统中实现是非常复杂和有趣的,笔者经过半年多的探险,终于知道了其中的秘密。 初识屏幕抓字 最初知道屏幕抓字,是在购买了《英汉通》软件之后。当时笔者还只是一个vb的初学者,对windows系统内部的知识了解并不多,认为在windows系统中屏幕抓字的实现应该和dos系统中的一样,调用一个dos中断取屏幕上的字符或直接读显示内存的内容就可以了。 看似很简单,其实不然 随着对windows系统的认识不断深入,才发现问题并不象想得那么简单。首先,翻阅了windows应用程序接口(api)中的上千个函数,并没有发现有一个现成的类似于getwordfrompoint()的函数;根据使用经验,经过判断发现,屏幕抓字采用的也不是图像识别技术;翻阅了sdk的联机文档中没有,ddk的联机文档中也没有;显示卡编程接口的资料则很难获得,有的也只是cga到vga显存的基本知识。回想当时坐在机子前,面对一屏屏的联机资料(如果是纸,将堆积如山),感觉就是在黑暗中的大海里航行,没有方向、没有灯光,但强烈的兴趣紧抓着我,一定要解开这个谜。 选择合适的编程工具 突然又有了一些新的想法:可否试着查询windows中关于字符的消息呢?dc(设备描述表)到底是什么?windows的textout函数是否将text放在dc的某个单元中? 显然,用vb就力不从心了。在dos中用turboc编程笔者还算熟练,因此先尝试用visualc++,但是奇慢的编译速度使人难以忍受,高度抽象的类让人一头雾水。望着一堆缠绕不清的类和消息,真有点牛刀宰鸡、刺刀耕田的感觉。 最后选择了delphi,第一印象是编译速度真快,在我的祖父型386机子上编译一个windows程序,速度和用turboc的速度感觉差不多,真让人兴奋得爱不释手。随着不断使用,发觉delphi真是一个好的快速开发工具(快速并不意味着简单粗糙,而是和windows系统有混然一体良好接口的表现),让初学者也很容易上手。调用各种windows函数(包括很多未公开的函数)都非常直接迅速。 山穷水尽疑无路 随着对windows系统了解的深入,我逐渐明白了在向屏幕输出文字时,windows系统仅仅只是对某个应用程序发送wm-paint消息,告诉该应用程序窗口用户区已经“无效”而需要重画。具体的“绘制”工作(选择字体,颜色,文字)由应用程序完成。 应用程序在处理wm-paint消息时,调用beginpaint和endpaint来获得和释放设备描述表,调用drawtext、exttextout、textout等函数在设备描述表中“绘制”文字。 应用程序“绘制”文字,就象学生(应用程序)奉命(获得wm-paint消息)用老师(windows)提供的画笔(drawtext exttextouttextout等)在黑板上画画一样,虽然大家能看到画的是什么字,但是画笔作为绘图工具并不知道画的是什么。老师(windows)不知道学生(应用程序)到底用什么字体,颜色,画哪些文字。 总之,windows并不知道应用程序“绘制”的是什么。“文字”对windows来说只是画笔留在黑板(屏幕)上的粉笔印,只是绘画的痕迹。“文字”只存在于应用程序的模块中,对windows系统是“不可见”的。 柳暗花明又一村 经过再三考虑,我联想到在dos系统中编程,会采取改变中断向量地址,设置新的中断向量的技术:如果系统调用这个中断,就会先进入新的中断服务程序,然后再调用原来的中断服务程序。 那末,在windows系统中也采取这种技术,使系统如果调用某个函数,先进入一个跟踪函数,取得原函数的参数,再调用原来的函数。听起来是否象病毒传染和发作?其实很多程序都采用过类似技术,大学毕业设计声卡时我就用过。 至此,我认识到应该放弃常规的思路,采取一些技巧,截获textout、exttextout等函数,使之转向我的跟踪函数,在此查看应用程序(学生)的堆栈中传递给画笔(textout、exttextout等函数)的参数,从而获得应用程序要在屏幕上写的“文字”。 “屏幕抓字”的实现 1.用setwindowshookex()安装鼠标钩子mouseproc; 2.在屏幕上移动鼠标时,系统就会调用鼠标钩子mouseproc; 3.进入mouseproc,获得鼠标的坐标(x,y),设置对textout()、exttextout()等的跟踪程序,用invalidaterect()告诉系统该点(x,y)“失效”; 4.系统发出wm-paint消息,指示该点(x,y)处的应用程序重绘“失效”的区域。 5.负责绘制该点()的应用程序在受到wm-paint消息后,就有机会调用textout()、exttextout()等函数。 6.调用的函数被拦截进入跟踪程序:设置好了的跟踪程序截获了该次调用,从应用程序的堆栈中取出该点(x,y)“文字”的指针; 7.从应用程序的数据段中将“文字”指针的内容取出,即完成了一次“屏幕抓字”; 8.退出跟踪程序,返回到鼠标钩子mouseproc; 9.在mouseproc中解除对textout()exttextout()的跟踪; 10.退出mouseproc鼠标钩子程序,控制权交给系统; 11.在屏幕上移动鼠标,开始下一次“屏幕抓字”,返回步骤2。 |
|||||||||||||