博客已迁移至:http://kulv.sinaapp.com/,这里不再使用
ATL与MFC消息分发机制的对比---由金山开源代码引出的思考 (一)
前几天刚看金山开源代码时写了一篇博客分析了一下其消息机制的实现方式。后来发现写的很多都是ATL里面的,最**的是犯了一个严重的错误,把ATL的窗口消息机制里面一个重要技术:实现HWND和对应窗口类this指针之间的映射的Thunk技术给忽略掉了。后来陈坤GG即时的提醒了我,先谢谢他了!
好了,步入正题,今天主要对比一下ATL和MFC是如何将窗口句柄HWND和对应的类的this指针映射的。
1. 先说一下为什么要映射:
我们自己写WIN32程序时从来没有映射呀,一般只是注册窗口的时候提供一个窗口过程,然后就在窗口过程里面做所有事情就可以了,为什么要映射呢?
我们知道WINDOW是用C写的,所有API不支持面向对象。可关键是ATL/MFC是一个框架,为了尽最大努力屏蔽编程上的繁琐步骤(注册窗口类,提供窗口过程,创建窗口,显示窗口···)和能够使开发人员能够用面向对象的方法来编程,享受极大的方便,就不得不在面向过程的操作系统API和面向对象编程框架直接搭个桥梁,这就是问题的开始···
之所以我们自己写的程序一般是一个主窗口对应一个窗口过程!所有不用关心这些了(而且我们一般WIN32编程也没有完全面向过程去写)。可是ATL/MFC不同,窗口过程不用我们提供,这样咱们编程就方便了,所有框架给我们提供了窗口过程,问题是,框架能为我们每个不同的窗口提供不同的窗口过程,向操纵系统注册吗?不能也无法实现。所以它只能向操纵系统提供一个统一的窗口过程,
1.对ATL来说是 CWindowImplBaseT< >::StartWindowProc这个静态函数,别忘了静态函数其实跟全局函数差不多,是基于类的,它没有this指针,编译器不会为它添加this指针。(其实这个过程有点曲折,待会说)
2.对MFC来说是注册时AfxDlgProc等,不过实际情况比这复杂,待会说。
不管注册时提供的窗口过程是谁,反正有一点是明确的:一定是一个全局函数或类的静态函数。
上面其实是很简单的。既然提供的都是一个相同的函数,那么不管哪个窗口有消息了,操作系统都会调用这一个函数!不同的是提供不同的参数,就是是HWND参数!该参数毫无疑问的标志了一个窗口。问题是,我们如何知道该窗口句柄所对应的窗口类是谁??一个简单的方法:if-else查表。对,窗口一多就很慢!
于是来到了我们讨论的重点:ATL/MFC是如何映射的?
一、先说ATL吧:
还是从源头来,先看怎么注册的:
具体是怎么注册的在我之前的文章里说过http://blog.csdn.net/hw_henry2008/archive/2011/05/22/6438153.aspx
这里简单回顾一下。
这里全部以对话框为基础,多文档也类似的。在DoModal函数里面,创建对话框时是这样的
上面的代码调用GetWndClassInfo得到窗口结构的基本信息,其中即设置了窗口处理函数有必要贴一下 GetWndClassInfo的代码,它返回一个窗口类的基本信息,其中就包括了窗口处理函数,这是我们用来向操作系统注册的回调函数。
从上面的代码看出:在向操纵系统注册的时候提供的窗口过程是StartWindowProc,在VC/atlmfc/include/atlwin.h里面。这样当第一个消息来的时候,操纵系统毫无疑问会调用我们的StartWindowProc上一次我看到这就没有怎么细看了以至于略过了重要的thunk技术。下面继续看该窗口过程
疑问来了,m_thunk是什么?每个窗口实例都有这么一个数据成员,定义如下
上面的代码再多说一下,Init其实就是初始化了thunk结构,把它初始化成这样:
先把堆栈上的4字节处的内容改成传入的对应HWND窗口类的this指针,然后跳转到函数指针proc处执行,其实为WindowProc。
为什么这能够实现呢?
我们知道,StartWindowProc用WindowProc和对应窗口过程的this指针初始化了thunk,然后把这个thunk的“内容”(其实是一段精心安排的汇编代码)强制转换成为窗口过程函数,然后向操作系统注册! 想想这回产生什么效果呢??对,从此以后,每当“该窗口”有消息到来的时候,操作系统会以参数:
( hWnd, uMsg, wParam, lParam) 理所当然的调用该地址处的“函数”!!说的细一点,操作系统内会进行如下动作:
(这里与函数的调用约定有关,不清楚的请参考http://blog.csdn.net/hw_henry2008/archive/2011/05/29/6453257.aspx)
--------------------------------------------------------------
-----------------------------------------------------------------
此时的堆栈状态为:
lParam
wParam
uMsg
hWnd <----esp+4
cs:eip <----esp寄存器
------------------------------------------------------------------
看到这我们大概知道了,thunk技术在此的用途其实就是:将堆栈中的hWnd改成对应的窗口类this指针,注意thunk结构是每个窗口实例一个的,所以其实这个thunk中不同的地方就只有move的源地址不同,即窗口实例this指针不同于是,以后的每一个消息,操作系统不再调用StartWindowProc函数了,取而代之调用thunk处的代码。
我们继续看WindowProc是如何取得这个this的,毕竟它也是个静态函数。
对于ATL的消息分配过程就是上面所说的了,关于thunk的一些细节可以参考我转载的博客。
此外需要稍微了解点汇编语言,调用约定。这些在我转载的博客中有参考.
基本图示如下:
另外补充一下,刚才在StartWindowProc中的代码:
_AtlWinModule.ExtractCreateWndData()我开始觉得会有线程竞争问题出现,刚刚看了下里面的实现,是没问题的,里面不断加了锁,而且还用了“每线程变量”似的处理。
二、MFC的实现方式:
限于篇幅,请看下页:http://blog.csdn.net/hw_henry2008/archive/2011/05/29/6453730.aspx