ATL与MFC消息分发机制的对比---由金山开源代码引出的思考(二)


博客已迁移至:http://kulv.sinaapp.com/,这里不再使用


ATL与MFC消息分发机制的对比---由金山开源代码引出的思考(二)

   接本文上一部分:http://blog.csdn.net/hw_henry2008/archive/2011/05/29/6453676.aspx

 

上面说完了ATL的消息分发方式,下面继续MFC的实现机制。

二、MFC消息分发的实现方式:

     首先来看注册窗口时:
     还是以对话框为例子,调用DoModal 后,进入:

      其实PreModal没干啥事,就是调用了下面的AfxHookWindowCreate函数,以及得到父窗口句柄

     AfxHookWindowCreate在MFC中起着很奇特的作用,也很有意思。先看第一行的 _afxThreadState.GetData();
     字面意思可以看出是得到属于线程的一些状态信息,不过如果大家看其实现的话就会发现有些字眼很熟悉,比如:(CThreadData*)TlsGetValue(m_tlsIndex); 对,TLS,线程局部存储,它借助window操作系统提供的线程局部存储来保存与线程相关的一些变量数据,因为我们知道,消息队列是基于线程的,也就是每个线程可以有一个消息队列。
     下面看重头戏:SetWindowsHookEx,::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId())也即给当前线程安装一个线程钩子。
 类型是WH_CBT,钩子函数为:_AfxCbtFilterHook。那么WH_CBT是说明意思呢?MSDN:
       WH_CBT :Installs a hook procedure that receives notifications useful to a computer-based training (CBT)
 application. For more information, see the CBTProc hook procedure
.
       _AfxCbtFilterHook也就是这个所谓的CBTProc钩子函数类型。
      The CBTProc hook procedure is an application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue. A computer-based training (CBT) application uses this hook procedure to receive useful notifications from the system.
      其实意思就是:给线程安装一个WH_CBT钩子,当激活,创建,销毁··一个窗口的时候回先调用这个钩子函数通知我们。什么意思,也就是在线程创建窗口时,接收第一个消息之前会调研我们这个函数。
      下面先别急着看钩子过程的处理方式,因为我们知道,现在它是不会被调研的,它一定会在creating窗口的时候在系统内部被调用。咱们继续回到DoModal的代码,HW_CBT钩子安装好了,下面也该创建窗口了吧?
      进入CreateDlgIndirect,很长的函数···

      恩,原来这样,CreateDialogIndirect创建一个对话框,提供的窗口过程是AfxDlgProc ! 不过也许我们不会忘记,AfxHookWindowCreate安装了一个WH_CBT钩子,会在创建窗口的时候同步调用我们的钩子函数。顺便说一下SendMessage和PostMessage的区别。前者是同步的,实际上也就是一般的调用,知道调用的函数完成后才返回;
    后者是把消息放到线程的消息队列里面,然后马上返回。这里操作系统会在创建窗口的过程中,发送WM_CREATE,WM_NCCREATE 消息之前调用钩子过程。因此保证了第一条消息的正确处理!!

      首先我们感兴趣的是FromHandlePermanent,其接收一个HWND句柄,然后返回一个窗口类的指针!!!这不就是我们想要了解的:
      MFC如何将窗口句柄和窗口实例联系起来的?

      看到这基本有点预感了,MFC对应窗口句柄和对应窗口类实例直接的映射是通过查表来实现的。那么,问题来了,这些映射什么时候加入的呢???
      明显的感觉:在创建窗口,还没有接收到WM_CREATE消息之前。不然如果没有映射怎么分发这条消息呀?发给哪个类?不知道。这个关键的建立映射的时期不正是钩子WH_CBT的钩子过程里吗??回到_AfxCbtFilterHook钩子函数中。pWndInit->Attach(hWnd)语句其实就是将当前句柄加入到当前线程的映射中。

      看到这基本大功告成了,反正我是相当失望了,强大的复杂的MFC竟然用查表的方式建立映射,不过还好,是哈希表。基础数据结构是CMapPtrToPtr,关于CHandleMap里面的实现这里就不多说啦,读者可以自行查看。其用哈希表实现,所以效率还算差强人意。不过相对ATL的thunk技术就要逊色写了,个人觉得。

      再补充一下_AfxCbtFilterHook钩子过程中关于窗口过程句柄的设置。AfxGetAfxWndProc函数返回的是AfxWndProcBase,后者的工作其实也就是转掉AfxWndProc函数。

      所以之后的窗口过程回调函数实际上就是AfxWndProcBase或AfxWndProc。在AfxWndProc中完成消息的分发。下面就简单了

 


      到此MFC的消息映射终于算是完成了。此后的事情就是在各种类继承层次中进行那漫长而又复杂的消息路由了。

     也许会有疑问:为什么必须得安装一个WH_CBT钩子?其实很简单,HWND到this的映射必须在WM_CREATE等消息到达窗口过程之前设置好,而且我们的应用程序得到窗口过程是在CreateWindow,CreateDialogIndirect系列函数返回时当做返回值传给应用程序的,可是API告诉我们,此函数返回之前会发送WM_CREATE等函数!毫无疑问,等这个函数返回时去设置映射已经晚了,唯一的方法是安装钩子。

总结一下:
      因为window操作系统对窗口过程的要求必须是静态或者全局的CALLBACK调用约定的函数,传入HWND参数标志对应的窗口。
      而ATL/MFC 力求用面向对象封装繁琐的WIN32面向过程的window应用程序开发方式。但是又只能给操作系统提供统一的窗口过程。所以势必得再HWND和窗口类实例的this指针直接建立映射。在统一的窗口回调过程中再把消息“分发”到对应的窗口类的成员函数中去。

     1. 对于ATL来说,其使用thunk技术巧妙的实现了这个目标。
     2. MFC则使用哈希表来建立映射。
     个人感觉这次ATL和MFC对弈的结果是ATL优于MFC,尽管后者哈希表可能能达到线性时间速度,但是thunk的技术还是要好点的。呵呵···

     说的不对的地方还请大家指教一下,谢谢了!欢迎一起讨论!

 

 

 

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号