Windows消息的封装之:this 在哪里(一)
2009-05-17 23:49
260 查看
一个类的成员函数跟一般函数是不同的,而类的成员函数隐式的包含了一个this指针。
好家伙,一句废话,这是地球人都知道的东西。可是,Windows API 要求的Callback 函数都不是成员函数,而在面向对象的世界里,没有this,日子会过得很苦闷。那么多的Windows Framwork 们,是如何处理这个问题的呢?最近重新翻了一下代码,顺手做个笔记吧。以下是第一集:MFC的实现。
一、背景知识
Windows Framwork 们要解决的最基本的一个问题,就是窗口消息的封装问题。漂亮的窗体对象与消息映射,一下子就把程序员从令人眼花的switch case 中解放出来,不知道给C++程序员们在面对C程序员时带来了多少优越感。下面且让我们从故事的最开始讲起,说一说最初的故事。
Windows 是怎么找到一个窗体的窗口函数的呢?原来,在每一类窗口在系统中注册的时候,都包含了执行窗口函数的指针。这个注册工作是通过RegisterClass API 完成的。
ATOM RegisterClass(
const WNDCLASS* lpWndClass
);
这里的WNDCLASS 结构中,就包含了这个窗口函数的指针。
typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc; // 这个就是窗口函数的指针了
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
这个窗口函数的原型如下:
LRESULT CALLBACK WindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
每当消息发给一个窗体的时候,系统自动调用对应的窗口函数。
二、MFC 中的消息封装
在MFC 中,承担WindowProc 任务的是AfxWndProc。这个函数在wincore.cpp。
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
...
// all other messages route through message map
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
...
if (pWnd == NULL || pWnd->m_hWnd != hWnd)
return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
这个函数最好玩的是这么一句:CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
好嘛,就是这个FromHandlePermanent(),一下子把窗口类和窗口句柄关联起来了。下面的不用看了。这个FromHandlePermanent()函数怎么实现的呢?我们继续深入看看。
CWnd* PASCAL CWnd::FromHandlePermanent(HWND hWnd)
{
CHandleMap* pMap = afxMapHWND();
CWnd* pWnd = NULL;
if (pMap != NULL)
{
// only look in the permanent map - does no allocations
pWnd = (CWnd*)pMap->LookupPermanent(hWnd);
ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);
}
return pWnd;
}
原来是查表,通过CHandleMap 来查找CWnd * 和HWND 的关联。现在我们可以差不多猜到MFC在这个问题上的完整思路了:
1、建立一个窗口句柄与窗口类指针的关联表;
2、在AfxWndProc得到系统的每次回调的时候(得到了一个新消息),在上面的关联表中查询这个得到消息的窗口句柄,到底对应哪一个窗口对象;
3、调用AfxCallWndProc,把消息投递到那个窗口对象的消息处理函数里面,从此以后的调用,this 就有了。
好了,现在剩下最后一个问题:这个表是什么时候填充数据的?这是一个很麻烦的事情,因为我们在CreateWindow API 返回的时候,才能知道一个窗口的句柄,在这之前一直是不知道的。但是,这个时候我们再来填充这个关联表,我们肯定就晚了。为什么呢?因为CreateWindow API 在调用过程中,会给新建的窗口发送WM_NCCREATE、WM_CREATE 等消息,假如等CreateWindow 函数返回,我们的窗口对象就拦截不到这些消息了。
所以,MFC使用了一些比较卑鄙的办法^_^ 它给窗口挂了一个WH_CBT 钩子。这个工作是在AfxHookWindowCreate 函数里面实现的,这个函数也在wincore.cpp里面。
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
if (pThreadState->m_pWndInit == pWnd)
return;
if (pThreadState->m_hHookOldCbtFilter == NULL)
{
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
if (pThreadState->m_hHookOldCbtFilter == NULL)
AfxThrowMemoryException();
}
...
pThreadState->m_pWndInit = pWnd;
}
这个函数中,需要注意的除了挂钩之外,还把当前窗口对象的指针,保存在了pThreadState 里面。这是一个线程局部存储的数据,就是所谓的tls。这个保存起来的指针,就会在AfxCbtFilterHook 函数里面,作为窗口对象的指针用来建立句柄跟对象指针的关联。
为了处理跟输入法的兼容,AfxCbtFilterHook代码又长又臭,我就不全部贴了。贴其中最关键的内容:
AFX_MANAGE_STATE(pWndInit->m_pModuleState);
// the window should not be in the permanent map at this time
ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
// connect the HWND to pWndInit...
pWndInit->Attach(hWnd);
...
pThreadState->m_pWndInit = NULL;
整个关联工作正是由 pWndInit->Attach(hWnd); 来实现的,关联结束之后,把pThreadState 里面的窗口指针置空,为本线程创建下一个窗口作准备。下面就是Attach 函数的实现了:
BOOL CWnd::Attach(HWND hWndNew)
{
ASSERT(m_hWnd == NULL); // only attach once, detach on destroy
ASSERT(FromHandlePermanent(hWndNew) == NULL);
// must not already be in permanent map
...
CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist
ASSERT(pMap != NULL);
pMap->SetPermanent(m_hWnd = hWndNew, this);
...
return TRUE;
}
好了,整个流程走到这里就差不多说明白了。这些部分《深入浅出MFC》也有些语焉不详,算一个补充吧。
三、结论
MFC 基本上是通过查表来实现对象指针和窗口句柄的关联的。实话实说,这个映射兼容性很好,但是效率实在不敢恭维,每个消息都要查表,这使得整个框架的效率大受影响。正是由于这个原因,新一代的Windows Frameworks 都采用了更新的(也更猥琐)的关联方法。到底是什么方法呢?且听下回分解。
我的百度博客:
http://hi.baidu.com/spaceblog/blog
相关文章推荐
- 【读书笔记】【Delphi高手突破】TControl与Windows消息的封装
- Delphi对Windows消息等的封装和窗体的实现
- TWinControl的刷新过程(5个非虚函数,4个覆盖函数,1个消息函数,默认没有双缓冲,注意区分是TCustomControl还是Windows原生封装控件,执行流程不一样)
- 模拟MFC和WTL对Windows窗口框架消息映射机制的封装代码
- 揭秘.NET对Windows消息循环的封装
- MFC消息封装分析---Windows系列1
- .Net中封装Windows 消息实现进程间通讯
- .NET对Windows消息循环的封装
- {转}Delphi对Windows消息等的封装和窗体的实现
- .Net中封装Windows 消息实现进程间通讯
- Error message when you try to modify or to delete an alternate access mapping in Windows SharePoint Services 3.0: "An update conflict has occurred, and you must re-try this action"
- windows 消息大全 与 虚拟键列表 HOOK专题
- windows中使用钩子拦截消息
- 细说UI线程和Windows消息队列
- windows 句柄 消息响应 窗口过程
- Windows 消息大全使用详解
- [转]Windows窗体消息汇总
- windows常用消息1
- 在Windows系统上实现轻量级的线程间及进程间消息队列
- 眼见为实(2):介绍Windows的窗口、消息、子类化和超类化