作者:liigo
原文链接:http://blog.csdn.net/liigo/archive/2009/08/30/4499138.aspx
转载请注明出处:http://blog.csdn.net/liigo
liigo: "易语言.尘土"界面库2.0版源代码分析系列
在前一篇分析文章中,我们已经找到了,"易语言.尘土"界面库2.0版内部所有窗口共用的消息处理函数(WndProc) —— EDust_WndProc(),并且指出:它是完成从“Windows窗口机制的结构化编程模式”到“易语言完全面向对象模式”转换的核心;经此转换,用户面对的就不再是赤裸裸的窗口句柄(HWND),而是经过高度抽象、有血有肉、有人情味的易语言窗口对象了;从一片荒蛮之地,来到了易语言的对象系统,生活就一下子丰富多彩起来,封装、继承、多态,终于可以大展身手了。
此篇将分析这个重中之重的 EDust_WndProc() 函数。下面直接给出其易语言源代码:
关于这个函数的功能,作者在第一行注释中就说的很清楚:“根据句柄找到对象。然后调用它的默认窗口过程”。这里我(liigo)具体展开分析一下。函数的第一行(哈希表_获取()),就是作者说的“根据句柄找到对象”;函数最后一行(置入代码()),就是作者说的“调用它(易窗口对象)的默认窗口过程”,这段机器码指令比较隐讳,下文重点分析;函数中间部分处理了两种特殊情况,一个是在窗口销毁时(WM_NCDESTROY)做一些清理工作(从全局哈希表中删除该窗口句柄到易窗口对象的映射,调用该对象“复位对象”方法),一个是在意外找不到易对象时报错并将当前消息交由Windows系统的默认窗口处理函数DefWindowProcA处理。通过以上分析,我们发现,此函数功能明确,逻辑清楚,注释分明,是典型的“漂亮代码”,绝对值得我们学习和鉴赏。只不过有两处调用“置入代码”直接嵌入X86机器指令,不太直观。下面,我重点分析最后那行“置入代码”。先把它翻译成汇编代码,如下图:
(上图为以下易语言代码的等价汇编代码:置入代码 ({ 131, 125, 252, 0, 116, 33, 255, 117, 20, 255, 117, 16, 255, 117, 12, 255, 117, 8, 255, 117, 252, 62, 139, 4, 36, 139, 0, 139, 0, 46, 255, 80, 8, 137, 236, 93, 194, 16, 0 }))
这段汇编代码的核心结构是一个条件判断指令(CMP),和一个函数调用指令(CALL)。其中,[epb-4] 表示所在函数(即“EDust_WndProc”)的第一个局部变量“对象指针”,[ebp+8], [ebp+C], [ebp+10], [ebp+14] 依次表示EDust_WndProc函数的四个参数 hwnd, msg, wparam, lparam。作者按照stdcall调用约定先把四个参数从后到前依次入栈,然后又把“对象指针”(在前篇我(liigo)已经指出,这个值其实是易语言对象变量的地址)入栈,最后调用一个函数地址。这个函数地址是什么呢?我们计算一下,依据CALL指令之前的那三个MOV指令,第一个MOV把“对象变量地址”放入EAX寄存器,第二个MOV把“对象内存首地址”放入EAX,第三个MOV把“对象虚函数表首地址”放入EAX,那么CALL指令参数[eax+8]就是“虚函数表中的第三项”,也就是该易语言对象第三个方法的函数地址。根据易语言对象的内存布局,无论对象是基类对象还是子类对象,其虚函数表开头总是存储着基类方法函数地址,而且前两个必是“_初始化”和“_销毁”。那么,这个CALL指令调用的“虚函数表中的第三项”,应该就是易尘土界面库所有窗口对象的基类“_窗口基类”除去构造函数(即“_初始化”)和析构函数(即“_销毁”)之外的第一个方法的函数地址,没错,它就是“_窗口基类.消息过程()”。同时,由于“_窗口基类.消息过程()”是一个虚函数(虚方法),实际被调用的可能是被子类重写/覆盖的同名方法,看吧,易语言面向对象机制在此生效。(注,以上内容多处涉及易语言对象内存布局,这对易语言用户是透明的,并且可能因易语言编译器重大升级而调整,普通用户无需深入了解。)
通过以上分析,我们可以进一步把上面的汇编代码翻译为伪易语言代码了(因为太接近系统底层了,易语言自身实际无法表达,必须借助于嵌入机器指令),如下图:
哈哈,越来越清晰了。我们翻山越岭上下求索,始终徘徊于结构化编程和面向对象编程的边缘,直到遇见“_窗口基类.消息过程()”,才终于柳暗花明又一村:我们已经走进易语言面向对象(OOP)的花花世界!
可是,为什么呢?基于函数的结构化模式处理Windows窗口消息有何弊端,基于对象和虚方法的面向对象模式又有何种优势?我们又何必辛苦走这一遭呢?且听下文分解。
liigo补记:包括此篇在内的最近三篇,涉及的易语言源代码中均接连出现诸如“置入代码(139, 69, 8, 137, 69, 252)”之类的“天书”(至于因何出现,我已在上一篇末尾给出原由)。可能会有朋友问及如何读懂此类代码,下面给出我的建议:把“置入代码”参数中的十进制数值依次转换成16进制数值,在OllyDBG中以二进制形式将其顺序写入(右键菜单 Binary -> Edit,Ctrl + E)任意打开的EXE文件中,就可以看到OllyDBG自动反汇编出的代码了。我随手用易语言写了一个批量转换10进制到16进制的小程序,其源码可在此下载(http://download.csdn.net/source/1617720),以下是其核心代码:
QQ群“★易.尘土 界面库★”(6997135)是一个专门的技术讨论区,那里有一群志同道合的朋友。
"易语言.尘土"界面库2.0作者龚辟愚MM的联系QQ:5964249。仅限技术交流。