﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-liuyz2006-文章分类-windows</title><link>http://www.blogjava.net/liuyz2006/category/54057.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 15 Dec 2013 22:22:37 GMT</lastBuildDate><pubDate>Sun, 15 Dec 2013 22:22:37 GMT</pubDate><ttl>60</ttl><item><title>Windows 消息处理机制与事件驱动</title><link>http://www.blogjava.net/liuyz2006/articles/407613.html</link><dc:creator>阿者</dc:creator><author>阿者</author><pubDate>Sun, 15 Dec 2013 08:33:00 GMT</pubDate><guid>http://www.blogjava.net/liuyz2006/articles/407613.html</guid><description><![CDATA[<div id="mainContent">
<div class="forFlow"><!--done-->

<div id="topics">
<div class="post">
<div class="clear"></div>
<div class="postBody">
<div id="cnblogs_post_body">
<p><strong>【SunXin.VC++深入】</strong><br />1.<strong>窗口（Windows）和句柄（<span lang="EN-US">HANDLE,handle</span></strong><span style="font-family: 宋体">）</span>：窗口句柄（<span lang="EN-US">HWND</span><span style="font-family: 宋体">）图标句柄（<span lang="EN-US">HICON</span><span style="font-family: 宋体">）、光标句柄（</span><span lang="EN-US">HCURSOR</span><span style="font-family: 宋体">）和画刷句柄（</span><span lang="EN-US">HBRUSH</span><span style="font-family: 宋体">）<br />2.<strong>消息，消息队列，消息循环，消息响应</strong><br />&nbsp;.OS将操作包装成Message<br /><span lang="EN-US">&nbsp;.typedef struct MSG {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HWND&nbsp;&nbsp; hwnd;&nbsp;//窗口句柄，即标示消息所属的窗口&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UINT&nbsp;&nbsp; message;//标示消息的类别，是鼠标还是键盘等 如鼠标左键按下消息是<span lang="EN-US">WM_LBUTTONDOWN</span><span style="font-family: 宋体">，键盘按下消息是</span><span lang="EN-US">WM_KEYDOWN</span><span style="font-family: 宋体">，字符消息是</span><span lang="EN-US">WM_CHAR</span><br /></span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WPARAM wParam;//消息的附加信息<br /></span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPARAM lParam;//消息的附加信息<br /></span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp; time;//消息投递到消息队列中的时间<br /></span><span lang="EN-US">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;POINT&nbsp; pt;//鼠标的当前位置<br /></span><span lang="EN-US">&nbsp;&nbsp; } MSG;<br />&nbsp;.<strong>消息队列</strong>，<span style="font-family: 宋体">每一个</span><span lang="EN-US">Windows</span><span style="font-family: 宋体">应用程序开始执行后，系统都会为该程序创建一个消息队列，这个消息队列用来存放该程序创建的窗口的消息<br />&nbsp;.进队消息(OS将产生的消息放在应用程序的消息队列中，让应用程序来处理)<br />&nbsp; 不进队消息(OS直接调用窗口的处理过程)<br /><strong>&nbsp;.<span lang="EN-US">Windows</span><span style="font-family: 宋体">应用程序的消息处理机制</span></strong><br /><span lang="EN-US">&nbsp; while(GetMessage(&amp;msg,NULL,0,0))</span><span lang="EN-US">{//接收到<span lang="EN-US">WM_QUIT</span><span style="font-family: 宋体">消息时，才返回</span><span lang="EN-US">0</span><br />&nbsp;&nbsp;&nbsp;&nbsp; </span><span lang="EN-US">TranslateMessage(&amp;msg);//对消息进行包装处理然后再以消息的形式投放到消息队列<br />&nbsp;&nbsp;&nbsp;&nbsp; </span><span lang="EN-US">DispatchMessage(&amp;msg);//消息回传给操作系统，由操作系统调用窗口过程函数对消息进行处理<br />&nbsp;</span><span lang="EN-US">}<br /><br />（1<span style="font-family: 宋体">）操作系统接收到应用程序的窗口消息，将消息投递到该应用程序的消息队列中。<br /></span><span style="font-family: 宋体">（</span>2<span style="font-family: 宋体">）应用程序在消息循环中调用</span>GetMessage<span style="font-family: 宋体">函数从消息队列中取出一条条的消息。取出后，以对消息进行一些预处理，如放弃对某些消息的响应，或者调用</span>TranslateMessage<span style="font-family: 宋体">产生新的消息<br /></span><span style="font-family: 宋体">（</span>3<span style="font-family: 宋体">）应用程序调用</span>DispatchMessage<span style="font-family: 宋体">，将消息回传给操作系统。<span style="color: red"><u style="color: #000000"><span style="font-family: 宋体">消息是由</span>MSG<span style="font-family: 宋体">结构体对象来</span></u></span></span><span style="font-family: 宋体">表示的，</span><span style="font-family: 宋体">其中就包含了</span><span style="font-family: 宋体">接收消息的窗口的句柄。因此，</span>DispatchMessage<span style="font-family: 宋体">函数总能进行正确的传递。</span><span style="font-family: 宋体">（</span>4<span style="font-family: 宋体">）系统利用</span>WNDCLASS<span style="font-family: 宋体">结构体的</span>lpfnWndProc<span style="font-family: 宋体">成员保存的窗口过程函数的指针调用窗口过程，对消息进行处理（即&#8220;系统给应用程序发送了消息&#8221;）。<br /><strong>&nbsp;.窗口过程函数<br /></strong><span lang="EN-US"><span lang="en-us">&nbsp;&nbsp; lresult callback windowproc(</span></p>
<p class="a0"><span lang="en-us">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hwnd hwnd,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 对应消息的窗口句柄</span></p>
<p class="a0"><span lang="en-us">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uint umsg,&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 消息代码，区别消息的类型</span></p>
<p class="a0"><span lang="en-us">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wparam wparam,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 消息代码的附加参数1</span></p>
<p class="a0"><span lang="en-us">&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lparam lparam &nbsp; &nbsp;&nbsp;&nbsp; // 消息代码的附加参数2</span></p>
<p class="a0"><span lang="en-us">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<br /><strong>【蔡学镛.揭开消息循环的神秘面纱】</strong></span></span></span></span></span></p>
<p class="a0"><img border="0" alt="" src="http://images.cnblogs.com/cnblogs_com/jeemhu/WinForm/090523-message-01.gif" width="481" height="362" jquery17006867730873122285="1" /><br />简单归纳如下：<br />&nbsp;&nbsp; 讯息循环被封装进了 Application 类别的 Run() 静态方法中；Windows Procedure 被封装进了 NativeWindow 与 Control 类别中；<br />个别的讯息处理动作被封装进 Control 类别的 OnXyz()（例如 OnPaint()）。我们可以覆盖（override）OnXyz()，来提供我们自己的程序。<br />也可以利用.NET的事件（event）机制，在 Xyz 事件上，加入我们的事件处理函式（Event Handler）。Control 类别的 OnXyz() 会主动呼叫 Xyz 事件的处理函式。<br />请注意，因为 Xyz 的事件处理函式是由 Control 类别的 OnXyz() 方法所呼叫的，所以当你覆写 OnXyz() 方法时，<br />不要忘了呼叫 Control 类别的 OnXyz()（除非你有特殊需求），否则 Xyz 事件处理函式将会没有作用。<br />只要呼叫 base.OnXyz()，就可以呼叫到 Control 类别的 OnXyz() 方法<br />1.&nbsp;在 Main() 中，利用 Application.Run() 来将 Form1 窗口显示出来，并进入讯息循环。程序的执行过程中，Application.Run() 一直未结束。 <br />2.&nbsp;OS 在此 Process 的讯息队列内放进一个 WM_PAINT 讯息，好让窗口被显示出来。 <br />3.&nbsp;WM_PAINT 被 Application.Run() 内的讯息循环取出来，发派到 WndProc()。由于多型（Polymorphism）的因素，此次调用（invoke）到的 WndProc() 是属于&nbsp;&nbsp;&nbsp; Form1 的 WndProc()，也就是上述程序中批注（comment）1 的地方，而不是调用到 Control.WndProc()。 <br />4.&nbsp;在 Form1.WndProc() 的最后，有调用 base.WndProc()，这实际上调用到 Control.WndProc()。 <br />5.&nbsp;Control.WndProc() 从 Message 参数中得知此讯息是 WM_PAINT，于是调用 OnPaint()。由于多型的因素，此次调用到的 OnPaint() 是属于 Form1 的 OnPaint()，也就是上述程序中批注 2 的地方，而不是调用到 Control.OnPaint()。 <br />6.&nbsp;在 Form1.OnPaint() 的最后，有调用 base.OnPaint()，这实际上调用到 Control.OnPaint()。 <br />7.&nbsp;我们曾经在 Form1 的建构式（constructor）中将 Form1_Paint() 与 Form1_Paint2() 登记成为 Paint 事件处理函式（Event Handler）。Control.OnPaint() 会去依序去呼叫这两个函式，也就是上述程序中批注 3 与 4 的地方。 <br /><br /><strong>【.NET Windows Message】<br /></strong>1.Control--Button,Form&#8230;&#8230;<br />&nbsp; protect vitrual WndProcess(ref Message);<br />&nbsp;&nbsp;&nbsp; 调用private Wm_(ref Message);//具体某类消息<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 调用Oprotect virtual On_xx(EventArg e);//触发相关事件<br />2.解释事件冒泡：比如键盘消息可先由Form来处理，然后交由相关的Control来处理<br />3.解释FormPaint：窗口的移动,最小化，最大话都会引起窗口内容的重新Paint,OS产生一个相关消息发给应用程序的消息队列，应用程序得到并处理消息时先是Form依次经过Wn_Process，Wn_..,On_Paint,Form_Paint，之后Form中的每一个Control也会依次做重绘的工作</p></span></span></span></div></div></div></div></div></div><img src ="http://www.blogjava.net/liuyz2006/aggbug/407613.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/liuyz2006/" target="_blank">阿者</a> 2013-12-15 16:33 <a href="http://www.blogjava.net/liuyz2006/articles/407613.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VCL Delphi Windows消息 窗口函数 注册 MakeObject (转自网络)</title><link>http://www.blogjava.net/liuyz2006/articles/407612.html</link><dc:creator>阿者</dc:creator><author>阿者</author><pubDate>Sun, 15 Dec 2013 08:27:00 GMT</pubDate><guid>http://www.blogjava.net/liuyz2006/articles/407612.html</guid><description><![CDATA[<div class="content-head clearfix">
<h2 class="title content-title">VCL Delphi Windows消息 窗口函数 注册 MakeObject (转自网络)</h2></div>
<div id="content" class="content mod-cs-content text-content clearfix">MakeObjectInstance函数是Delphi函数，这个函数的申明部分是这样的：<br />function MakeObjectInstance(Method: TWndMethod): Pointer;<br />和它相对应的另一个函数叫做FreeObjectInstance，它的申明部分是这样的：<br />procedure FreeObjectInstance(ObjectInstance: Pointer);<br /><br />MakeObjectInstance函数在Delphi的帮助文件中并没有，但是它还是在Interface中申明了，<br />也就是说我们可以使用它。<br /><br />要完全解释清楚这个函数的作用不是三言两语的事情，<br />现贴上一篇文章：<br /><br />关键字:<br />VCL Delphi Windows消息 窗口函数 注册 MakeObject 汇编 贴文时间<br />2001-7-9 17:16:31 文章类型: <br />原作 给贴子投票 <br />投票 <br />cheka 原作 出处: <br /><br /><br />VCL HardCore &#8212;&#8212;VCL窗口函数注册机制研究手记，兼与MFC比较<br /><br />By cheka cheka@yeah.net （转载请保留此信息） <br /><br />这个名字起的有些耸人听闻，无他意，只为吸引眼球而已，如果您对下列关键词有兴趣，希望不要错过本文：<br /><br />1. VCL可视组件在内存中的分页式管理;<br /><br />2. 让系统回调类的成员方法<br /><br />3. Delphi 中汇编指令的使用<br /><br />4. Hardcore <br /><br />5. 第4条是骗你的<br /><br /><br /><br />我们知道Windows平台上的GUI程序都必须遵循Windows的消息响应机制，可以简单概括如下，所有的窗口控件都向系统注册自身的窗口函数，运行期间消息可被指派至特定窗口控件的窗口函数处理。对消息相应机制做这样的概括有失严密，请各位见谅，我想赶紧转向本文重点，即在利用Object Pascali或是C++这样的面向对象语言编程中，如何把一个类的成员方法向系统注册以供回调。 <br /><br />在注册窗口类即调用RegisterClass函数时，我们向系统传递的是一个WindowProc 类型的函数指针<br /><br />WindowProc 的定义如下<br /><br />LRESULT CALLBACK WindowProc(<br /><br />HWND hwnd, // handle to window<br /><br />UINT uMsg, // message identifier<br /><br />WPARAM wParam, // first message parameter<br /><br />LPARAM lParam // second message parameter<br /><br />)；<br /><br />如果我们有一个控件类，它拥有看似具有相同定义的成员方法TMyControl.WindowProc,可是却不能够将它的首地址作为lpfnWndProc参数传给RegisterClass，道理很简单，因为Delphi中所有类成员方法都有一个隐含的参数，也就是Self，因此无法符合标准 WindowProc 的定义。<br /><br />那么，在VCL中，控件向系统注册时究竟传递了一个什么样的窗口指针，同时通过这个指针又是如何调到各个类的事件响应方法呢？我先卖个关子，先看看MFC是怎么做的。<br /><br />在调查MFC代码之前，我作过两种猜想：<br /><br />一，作注册用的函数指针指向的是一个类的静态方法，<br /><br />静态方法同样不需要隐含参数 this （对应 Delphi中的 Self ,不过Object Pascal不支持静态方法）<br /><br />二，作注册用的函数指针指向的是一个全局函数，这当然最传统，没什么好说的。<br /><br />经过简单的跟踪，我发现MFC中，全局函数AfxWndProc是整个MFC程序处理消息的&#8220;根节点&#8221;，也就是说，所有的消息都由它指派给不同控件的消息响应函数，也就是说，所有的窗口控件向系统注册的窗口函数很可能就是 AfxWndProc （抱歉没做深入研究，如果不对请指正）。而AfxWndProc 是如何调用各个窗口类的WndProc呢？<br /><br />哈哈，MFC用了一种很朴素的机制，相比它那么多稀奇古怪的宏来说，这种机制相当好理解：使用一个全局的Map数据结构来维护所有的窗口对象和Handle(其中Handle为键值），然后AfxWndProc根据Handle来找出唯一对应的窗口对象（使用静态函数CWnd::FromHandlePermanent(HWND hWnd) ），然后调用其WndProc，注意WndProc可是虚拟方法，因此消息能够正确到达所指定窗口类的消息响应函数并被处理。<br /><br />于是我们有理由猜想VCL也可能采用相同的机制，毕竟这种方式实现起来很简单。我确实是这么猜的，不过结论是我错了......<br /><br />开场秀结束，好戏正式上演。<br /><br />在Form1上放一个Button(缺省名为Button1),在其OnClick事件中写些代码，加上断点，F9运行，当停留在断点上时，打开Call Stack窗口（View-&gt;Debug Window-&gt;Call Stack， 或者按Ctrl-Alt-S )可看到调用顺序如下（从底往上看，stack嘛）<br /><br />( 如果你看到的 Stack 和这个不一致，请打开DCU 调试开关 Project-&gt;Options-&gt;Compiler-&gt;Use Debug DCUs, 这个开关如果不打开，是没法调试VCL源码的 )<br /><br />TForm1.Button1Click(???)<br /><br />TControl.Click<br /><br />TButton.Click<br /><br />TButton.CNCommand ((48401, 3880, 0, 3880, 0))<br /><br />TControl.WndProc ((48401, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />TWinControl.WndProc ((48401, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />TButtonControl.WndProc ((48401, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />TControl.Perform (48401,3880,3880)<br /><br />DoControlMsg (3880,(no value))<br /><br />TWinControl.WMComman d((273, 3880, 0, 3880, 0))<br /><br />TCustomForm.WMCommand ((273, 3880, 0, 3880, 0))<br /><br />TControl.WndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />TWinControl.WndProc((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />TCustomForm.WndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />TWinControl.MainWndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))<br /><br />StdWndProc (3792,273,3880,3880)<br /><br />可见 StdWndProc 看上去象是扮演了MFC中 AfxWndProc 的角色，不过我们先不谈它，如果你抑制不住好奇心，可以提前去看它的源码，在Forms.pas中，看到了么? 是不是特~~~~别有趣阿。<br /><br />实际上，VCL在RegisterClass时传递的窗口函数指针并非指向StdWndProc。那是什么呢？<br /><br />我跟，我跟，我跟跟跟，终于在Controls.pas的TWindowControl的实现代码中<br /><br />（procedure TWinControl.CreateWnd;) 看到了RegisterClass的调用,hoho，终于找到组织了......别忙，发现了没，这时候注册的窗口函数是InitWndProc，看看它的定义，嗯，符合标准，再去瞧瞧代码都干了些什么。<br /><br />发现这句：<br /><br />SetWindowLong(HWindow, GWL_WNDPROC,Longint(CreationControl.FObjectInstance));<br /><br />我Faint，搞了半天InitWndProc初次被调用（对每一个Wincontrol来说）就把自个儿给换了，新上岗的是FObjectInstance。下面还有一小段汇编，是紧接着调用FObjectInstance的，调用的理由不奇怪，因为以后调用FObjectInstace都由系统CallBack了，但现在还得劳InitWndProc的大驾去call。调用的方式有些讲究，不过留给您看完这篇文章后自个儿琢磨去吧。<br /><br />接下来只能继续看FObjectInstance是什么东东，它定义在 TWinControl 的 Private 段，是个Pointer也就是个普通指针，当什么使都行，你跟Windows说它就是 WndProc 型指针 Windows 拿你也没辙。<br /><br />FObjectInstance究竟指向何处呢，镜头移向 TWincontrol 的构造函数，这是FObjectInstance初次被赋值的地方。 多余的代码不用看，焦点放在这句上<br /><br />FObjectInstance := MakeObjectInstance(MainWndProc); <br /><br />可以先告诉您，MakeObjectInstance是本主题最精彩之处，但是您现在只需知道FObjectInstance&#8220;指向了&#8221;MainWndProc，也就是说通过某种途径VCL把每个MainWndProc作为窗口函数注册了，先证明容易的，即 MainWndProc 具备窗口函数的功能，来看代码：<br /><br />( 省去异常处理 )<br /><br />procedure TWinControl.MainWndProc(var Message: TMessage);<br /><br />begin<br /><br />WindowProc(Message);<br /><br />FreeDeviceContexts;<br /><br />FreeMemoryContexts;<br /><br />end; <br /><br />FreeDeviceContexts; 和 FreeMemoryContexts 是保证VCL线程安全的，不在本文讨论之列，只看WindowProc(Message); 原来 MainWndProc 把消息委托给了方法 WindowProc处理，注意到 MainWndProc 不是虚拟方法，而 WindowProc 则是虚拟的，了解 Design Pattern 的朋友应该点头了，嗯，是个 Template Method ， 很自然也很经典的用法，这样一来所有的消息都能准确到达目的地，也就是说从功能上看 MainWndProc 确实可以充作窗口函数。您现在可以回顾一下MFC的 AfxWindowProc 的做法，同样是利用对象的多态性，但是两种方式有所区别。 <br /><br />是不是有点乱了呢，让我们总结一下，VCL 注册窗口函数分三步： <br /><br />1. [ TWinControl.Create ]<br /><br />FObjectInstance 指向了 MainWndProc<br /><br />2. [ TWinControl.CreateWnd ] <br /><br />WindowClass.lpfnWndProc 值为 @InitWndProc; <br /><br />调用Windows.RegisterClass(WindowClass)向系统注册<br /><br />3. [ InitWndProc 初次被Callback时 ]<br /><br />SetWindowLong(HWindow, GWL_WNDPROC, Longint(CreationControl.FObjectInstance)) <br /><br />窗口函数被偷梁换柱，从此 InitWndProc 退隐江湖<br /><br />（注意是对每个TWinControl控件来说，InitWndProc 只被调用一次） <br /><br />前面说过，非静态的类方法是不能注册成为窗口函数的，特别是Delphi中根本没有静态类方法，那么MainWndProc 也不能有特权（当然宝兰可以为此在编译器上动点手脚，如果他们不怕成为呕像的话）。 <br /><br />那么，那么，您应该意识到了，在幕后操纵一切的，正是...... <br /><br />背景打出字幕 <br /><br />超级巨星：麦克奥布吉特因斯坦斯 <br /><br />（MakeObjectInstance） <br /><br />天空出现闪电，哦耶，主角才刚刚亮相。 <br /><br />废话不说，代码伺候： <br /><br />（ 原始码在 Form.pas 中，&#8220;{}&#8221;中是原始的注释，而&#8220; file://&#8221;/ 后的是我所加，您可以直接就注释代码，也可以先看我下面的评论，再回头啃code ） <br /><br />// 共占 13 Bytes<br /><br />type<br /><br />PObjectInstance = ^TObjectInstance;<br /><br />TObjectInstance = packed record<br /><br />Code: Byte; // 1 Byte<br /><br />Offset: Integer; // 4 Byte<br /><br />case Integer of <br /><br />0: (Next: PObjectInstance); // 4 Byte<br /><br />1: (Method: TWndMethod); // 8 Byte <br /><br />// TWndMethod 是一个指向对象方法的指针，<br /><br />// 事实上是一个指针对，包含方法指针以<br /><br />// 及一个对象的指针（即Self ）<br /><br />end;<br /><br /><br /><br />// 313是满足整个TInstanceBlock的大小不超过4096的最大值<br /><br />InstanceCount = 313; <br /><br />// 共占 4079 Bytes<br /><br />type<br /><br />PInstanceBlock = ^TInstanceBlock;<br /><br />TInstanceBlock = packed record<br /><br />Next: PInstanceBlock; // 4 Bytes<br /><br />Code: array[1..2] of Byte; // 2 Bytes<br /><br />WndProcPtr: Pointer; // 4 Bytes<br /><br />Instances: array[0..InstanceCount] of TObjectInstance; 313 * 13 = 4069<br /><br />end; <br /><br />function CalcJmpOffset(Src, Dest: Pointer): Longint;<br /><br />begin<br /><br />Result := Longint(Dest) - (Longint(Src) + 5);<br /><br />end; <br /><br />function MakeObjectInstance(Method: TWndMethod): Pointer;<br /><br />const<br /><br />BlockCode: array[1..2] of Byte = (<br /><br />$59, { POP ECX }<br /><br />$E9); { JMP StdWndProc } // 实际上只有一个JMP<br /><br />PageSize = 4096;<br /><br />var<br /><br />Block: PInstanceBlock;<br /><br />Instance: PObjectInstance;<br /><br />begin<br /><br />// InstFreeList = nil 表明一个Instance block已被占满，于是需要为一个新<br /><br />// Instance block分配空间，一个个Instance block通过PinstanceBlock中的<br /><br />// Next 指针相连，形成一个链表，其头指针为InstBlockList <br /><br />if InstFreeList = nil then<br /><br />begin<br /><br />// 为Instance block分配虚拟内存，并指定这块内存为可读写并可执行<br /><br />// PageSize 为4096。<br /><br />Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);<br /><br />Block^.Next := InstBlockList;<br /><br />Move(BlockCode, Block^.Code, SizeOf(BlockCode));<br /><br />Block^.WndProcPtr := Pointer(CalcJmpOffset(@Block^.Code[2], @StdWndProc)); <br /><br />// 以下代码建立一个Instance的链表<br /><br />Instance := @Block^.Instances;<br /><br />repeat<br /><br />Instance^.Code := $E8; { CALL NEAR PTR Offset }<br /><br />file://算/出相对 jmp StdWndProc指令的偏移量，放在$E8的后面<br /><br />Instance^.Offset := CalcJmpOffset(Instance, @Block^.Code);<br /><br />Instance^.Next := InstFreeList;<br /><br />InstFreeList := Instance;<br /><br />// 必须有这步，让Instance指针移至当前instance子块的底部<br /><br />Inc(Longint(Instance), SizeOf(TObjectInstance));<br /><br />// 判断一个Instance block是否已被构造完毕<br /><br />until Longint(Instance) - Longint(Block) &gt;= SizeOf(TInstanceBlock);<br /><br />InstBlockList := Block;<br /><br />end;<br /><br />Result := InstFreeList; <br /><br />Instance := InstFreeList;<br /><br />InstFreeList := Instance^.Next;<br /><br />Instance^.Method := Method;<br /><br />end; <br /><br />不要小看这区区几十行代码的能量，就是它们对 VCL 的可视组件进行了分页式管理，（代码中对两个链表进行操作，InstanceBlock 中有 ObjectInstance 的链表，而一个个InstanceBlock 又构成一个链表 ）一个 InstanceBlock 为一页，有4096 字节，虽然 InstanceBlock 实际使用的只有 4079 字节，不过为了 Alignment ,就加了些 padding 凑满 4096 。从代码可见每一页中可容纳 313 个所谓的ObjectInstance，如果望文生义很容易将这个 ObjectInstance 误解为对象实例，其实不然，每个ObjectInstance 其实是一小段可执行代码，而这些可执行代码不是编译期间生成的，也不是象虚拟函数那样滞后联编，而根本就是MakeObjectInstance 在运行期间&#8220;创作&#8221;的（天哪）! 也就是说，MakeObjectInstance 将所有的可视VCL组件 改造成了一页页的可执行代码区域，是不是很了不起呢。 <br /><br />不明白ObjectInstance所对应的代码是做什么的么？没关系，一起来看 <br /><br />call - - - - - - - - - - - &gt; pop ECX // 在call 之前，下一个指令地址会被压栈<br /><br />@MainWndProc // 紧接着执行pop ECX, 为何这么做呢？<br /><br />@Object(即Self) // 前面注释中提过 <br /><br />答案在 StdWndProc 的代码中，要命哦，全是汇编，可是无限风光在险峰，硬着头皮闯一回吧。 <br /><br />果不其然，我们发现其中用到了ECX<br /><br />function StdWndProc(Window: HWND; Message, WParam: Longint;<br /><br />LParam: Longint): Longint; stdcall; assembler;<br /><br />asm<br /><br />XOR EAX,EAX <br /><br />PUSH EAX<br /><br />PUSH LParam<br /><br />PUSH WParam<br /><br />PUSH Message<br /><br />MOV EDX,ESP<br /><br />MOV EAX,[ECX].Longint[4] // 相当于 MOV EAX, [ECX+4] ( [ECX+4] 是什么？就是Self )<br /><br />CALL [ECX].Pointer // 相当于 CALL [ECX] , 也就是调用 MainWndProc<br /><br />ADD ESP,12<br /><br />POP EAX<br /><br />end; <br /><br />这段汇编中在调用MainWndProc前作了些参数传递的工作，由于MainWndProc 的定义如下<br /><br />procedure TwinControl..MainWndProc(var Message: TMessage); <br /><br />根据Delphi 的约定，这种情况下隐函数Self 作为第一个参数，放入EAX 中，TMessage 结构的指针作为第二个参数，放入EDX中，而Message的指针从哪儿来呢？我们看到在连续几个 Push 之后，程序已经在堆栈中构造了一个TMessage 结构，而这时的ESP 当然就是这个结构的指针，于是将它赋给EDX 。如果您不熟悉这方面的约定，可以参考Delphi 的帮助Object Pascal Refrence -&gt; Program Control。 <br /><br />现在真相大白，Windows 消息百转千折终于传进MainWndProc , 不过这一路也可谓相当精彩，MakeObject这一函数自然是居功至伟， StdWndProc 也同样是幕后英雄，让我们把 MakeObjectInstance 作出的代码和StdWndProc 连接起来，哦，堪称鬼斧神工。<br />( 图片无法显示，请下载全文）<br /><br />就此在总结一下， FobjectInstance 被VCL 注册为窗口函数，而实际上 FObjectInstance 并不实际指向某个函数，而是指向一个ObjectInstance, 而后者我们已经知道是一系列相接的可执行代码段当中的一块，当系统需要将 FObjectInstance 当做窗口函数作为回调时，实际进入了ObjectInstance 所在的代码段，然后几番跳跃腾挪（一个call 加一个 jump ）来到StdWndProc ，StdWndProc 的主要功用在于将Self 指针压栈，并把Windows的消息包装成Delphi的TMessage 结构，如此才能成功调用到TWinControl类的成员方法 MainWndProc, 消息一旦进入MainWndProc 便可以轻车熟路一路高唱小曲来到各个对象转属的WndProc , 从此功德圆满。 <br /><br />后记： <br /><br />个人感觉在这一技术上VCL 要比MFC 效率高出不少，后者每次根据窗口句柄来检索相对应的窗口对象指针颇为费时，同时MakeObject 的代码也相当具有参考价值，有没有想过让你自己的程序在内存中再开一堆可执行代码？ <br /><br />所有的代码是基于Delphi5的，可能与其余版本有所出入，但相信不会很大。 <br /><br />整个星期六和星期天我都花在写作此文上了（连调试带写字）， 不过水平所限，难免有所错误与表达不周，但愿不至以己昏昏令人昏昏，欢迎来信探讨指教 cheka@yeah.net , thanx </div><img src ="http://www.blogjava.net/liuyz2006/aggbug/407612.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/liuyz2006/" target="_blank">阿者</a> 2013-12-15 16:27 <a href="http://www.blogjava.net/liuyz2006/articles/407612.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>