ATL消息处理机制_第1页
ATL消息处理机制_第2页
ATL消息处理机制_第3页
ATL消息处理机制_第4页
ATL消息处理机制_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

ATL消息机制的探究作者:后知后觉(307817387)任何的框架,包括MFC或者ATL,创建并显示窗口,处理窗口消息都逃不过RegisterClass、CreateWindow和Message Loop。对于ATL也是一样的道理,下面就来细说一下ATL的消息处理机制。重要的部分我会用红色标识出来。1,注册窗口类CWindowImpl类使用一个DECLARE_WND_CLASS(NULL)的宏来定义WNDCLASS的信息#define DECLARE_WND_CLASS(WndClassName) static ATL:CWndClassInfo& GetWndClassInfo() static ATL:CWndClassInfo wc = sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL , NULL, NULL, IDC_ARROW, TRUE, 0, _T() ; return wc; 这里有一个很重要的信息,那就是StartWindowProc,这个是定义的默认的窗口处理函数。先提醒一下,后面有具体说明。CWndClassInfo的定义如下:struct _ATL_WNDCLASSINFOWWNDCLASSEXW m_wc;LPCWSTR m_lpszOrigName;WNDPROC pWndProc;LPCWSTR m_lpszCursorID;BOOL m_bSystemCursor;ATOM m_atom;WCHAR m_szAutoName5+sizeof(void*)*CHAR_BIT;ATOM Register(WNDPROC* p)return AtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule, this, p);其中的Register方法会注册一个窗口类。在CWindowImpl的Create方法中:HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,DWORD dwStyle = 0, DWORD dwExStyle = 0,_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)if (T:GetWndClassInfo().m_lpszOrigName = NULL)T:GetWndClassInfo().m_lpszOrigName = GetWndClassName();ATOM atom = T:GetWndClassInfo().Register(&m_pfnSuperWindowProc); dwStyle = T:GetWndStyle(dwStyle);dwExStyle = T:GetWndExStyle(dwExStyle);/ set captionif (szWindowName = NULL)szWindowName = T:GetWndCaption();return CWindowImplBaseT:Create(hWndParent, rect, szWindowName,dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);这里Register的里面的具体实现很复杂,不过其本质就是注册窗口类,具体里面的实现的作用就是根据GetWndClassInfo里面定义的结构体里面的内容生成一个WNDCLASSEX的信息,窗口过程函数也是同理,然后注册成窗口类。同时对传入的m_pfnSuperWindowProc赋值,其值就是定义的StartWindowProc。自此,可以的出,创建窗口生成消息之后第一步会到StartWindowProc中去执行。具体StartWindowProc是什么呢?其实它是CWindowImplBaseT里面定义的一个静态函数:static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);T:GetWndClassInfo函数就是调用DECLARE_WND_CLASS宏定义的方法,其本质就是返回一个定义好的CWndClassInfo对象。这里在创建窗口之前先调用Register方法注册一个窗口类,上面也说到了过。这里的T:GetWndClassInfo和T:GetWndStyle这些方法的调用很巧妙,它有利的避开了虚函数机制同时能够实现动态调用的功效。T就是我们自定义的类如CMainDlg,CMainDlg继承自CWindowImpl,CWindowImpl继承自CWindowImplBaseT,CWindowImplBaseT继承自CWindowImplRoot,最终CWindowImplRoot继承自TBase也就是后面的类中传递进来的CWindow,同事也继承了CMessage。CWindow用来封装HWND,CMessage用来封装消息循环。总之T就是我们定义的最终的子类CMainDlg,所以继承链中任何一个类定义了GetWndClassInfo或者GetWndStyle方法,T:GetWndClassInfo或者T:GetWndStyle就可以调用到该方法,这样的话,如果子类要重写该方法,即使在父类中调用T:GetWndClassInfo或T:GetWndStyle也是调用子类重写过的版本,实现“多态”的效果。2,创建窗口窗口类注册好了之后调用 CWindowImplBaseT的Create方法:template HWND CWindowImplBaseT:Create(HWND hWndParent, _U_RECT rect, LPCTSTR szWindowName,DWORD dwStyle, DWORD dwExStyle, _U_MENUorID MenuOrID, ATOM atom, LPVOID lpCreateParam) BOOL result;ATLASSUME(m_hWnd = NULL); / Allocate the thunk structure here, where we can fail gracefully.result = m_thunk.Init(NULL,NULL);if (result = FALSE) SetLastError(ERROR_OUTOFMEMORY);return NULL;if(atom = 0)return NULL;_AtlWinModule.AddCreateWndData(&m_thunk.cd, this);if(MenuOrID.m_hMenu = NULL & (dwStyle & WS_CHILD)MenuOrID.m_hMenu = (HMENU)(UINT_PTR)this;if(rect.m_lpRect = NULL)rect.m_lpRect = &TBase:rcDefault;HWND hWnd = :CreateWindowEx(dwExStyle, MAKEINTATOM(atom), szWindowName,dwStyle, rect.m_lpRect-left, rect.m_lpRect-top, rect.m_lpRect-right - rect.m_lpRect-left,rect.m_lpRect-bottom - rect.m_lpRect-top, hWndParent, MenuOrID.m_hMenu,_AtlBaseModule.GetModuleInstance(), lpCreateParam);ATLASSUME(m_hWnd = hWnd);return hWnd;A,_AtlWinModule.AddCreateWndData(&m_thunk.cd, this)的说明:这里有几个重要的地方,第一点就是每一个CWindowImpl保存了一个变量m_thunk,这个变量很重要,它的类型是CWndProcThunk,定义如下:class CWndProcThunkpublic:_AtlCreateWndData cd;CStdCallThunk thunk;BOOL Init(WNDPROC proc, void* pThis)return thunk.Init(DWORD_PTR)proc, pThis);WNDPROC GetWNDPROC()return (WNDPROC)thunk.GetCodeAddress();其中cd的定义如下:struct _AtlCreateWndDatavoid* m_pThis;DWORD m_dwThreadID;_AtlCreateWndData* m_pNext;_AtlCreateWndData的本质就是链表的一个节点,有一个指向后面节点的指针m_pNext。m_pThis用来保存当前对象也就是生成的CMainDlg对象。m_dwThreadID用来保存当前的线程ID。下面就来说明一下保存这两个值的具体作用。这里有一句很重要的代码 _AtlWinModule.AddCreateWndData(&m_thunk.cd, this);具体是做什么的呢? _AtlWinModule是一个CAtlWinModule的对象,CAtlWindModule继承自_ATL_WIN_MODULE,_ATL_WIN_MODULE的定义如下:struct _ATL_WIN_MODULE70UINT cbSize;CComCriticalSection m_csWindowCreate;_AtlCreateWndData* m_pCreateWndList;CSimpleArray m_rgWindowClassAtoms;typedef _ATL_WIN_MODULE70 _ATL_WIN_MODULE;这里可以看出,_AtlWinModlue其实保存了一个_AtlCreateWndData的指针,这个指针就是用来构建一个窗口类链表的头结点。_AtlWinModule.AddCreateWndData(&m_thunk.cd, this);的作用就是将刚刚新建的this(就是CMainDlg的对象的指针)保存到链表的头部。这句话的具体实现是这样的:void AddCreateWndData(_AtlCreateWndData* pData, void* pObject)AtlWinModuleAddCreateWndData(this, pData, pObject);ATLINLINE ATLAPI_(void) AtlWinModuleAddCreateWndData(_ATL_WIN_MODULE* pWinModule, _AtlCreateWndData* pData, void* pObject)if (pWinModule = NULL)_AtlRaiseException(DWORD)EXCEPTION_ACCESS_VIOLATION);ATLASSERT(pData != NULL & pObject != NULL);if(pData = NULL | pObject = NULL)_AtlRaiseException(DWORD)EXCEPTION_ACCESS_VIOLATION);pData-m_pThis = pObject;pData-m_dwThreadID = :GetCurrentThreadId();CComCritSecLock lock(pWinModule-m_csWindowCreate, false);if (FAILED(lock.Lock()ATLTRACE(atlTraceWindowing, 0, _T(ERROR : Unable to lock critical section in AtlWinModuleAddCreateWndDatan);ATLASSERT(0);return;pData-m_pNext = pWinModule-m_pCreateWndList;pWinModule-m_pCreateWndList = pData;这里的最后两句话可以很清楚的看到,已经将刚刚生成的_AtlCreateWndData节点也就是m_thunk里面的cd值(这里保存的就是当前对象CMainDlg)添加到了全局的_AtlWinModlue的链表的头部。对于_AtlWinModule.AddCreateWndData的说明就先到这里,下文将说明保存保存这个指针的意义之所在。回到上面创建的过程里面,现在已经将目前的CMainDlg对象的信息保存在了全局链表的头部,之后就使用CreateWindowEx方法来创建真正的窗口了。HWND hWnd = :CreateWindowEx(dwExStyle, MAKEINTATOM(atom), szWindowName,dwStyle, rect.m_lpRect-left, rect.m_lpRect-top, rect.m_lpRect-right - rect.m_lpRect-left,rect.m_lpRect-bottom - rect.m_lpRect-top, hWndParent, MenuOrID.m_hMenu,_AtlBaseModule.GetModuleInstance(), lpCreateParam);在创建的时候会触发Windows消息如WM_NCCREATE,WM_CREATE等消息,很自然的,这些消息肯定会由窗口过程函数来处理。在最初的窗口类定义中可以看到入口函数为StartWindowProc,它是一个静态的成员函数,定义在CWindowImplBaseT中。此时消息会先由这个函数处理,内部逻辑如下:template LRESULT CALLBACK CWindowImplBaseT:StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)CWindowImplBaseT* pThis = (CWindowImplBaseT*)_AtlWinModule.ExtractCreateWndData();ATLASSERT(pThis != NULL);if(!pThis)return 0;pThis-m_hWnd = hWnd;pThis-m_thunk.Init(pThis-GetWindowProc(), pThis);WNDPROC pProc = pThis-m_thunk.GetWNDPROC();WNDPROC pOldProc = (WNDPROC):SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);#ifdef _DEBUGif(pOldProc != StartWindowProc)ATLTRACE(atlTraceWindowing, 0, _T(Subclassing through a hook discarded.n);#else(pOldProc);/ avoid unused warning#endifreturn pProc(hWnd, uMsg, wParam, lParam);第一句_AtlWinModule.ExtractCreateWndData();先将刚刚保存在链表头部的节点取出来,其结果就是得到刚刚添加到头部的对象指针。B, _AtlWinModule.ExtractCreateWndData()的说明void* ExtractCreateWndData()return AtlWinModuleExtractCreateWndData(this);ATLINLINE ATLAPI_(void*) AtlWinModuleExtractCreateWndData(_ATL_WIN_MODULE* pWinModule)if (pWinModule = NULL)return NULL;void* pv = NULL;CComCritSecLock lock(pWinModule-m_csWindowCreate, false);if (FAILED(lock.Lock()ATLTRACE(atlTraceWindowing, 0, _T(ERROR : Unable to lock critical section in AtlWinModuleExtractCreateWndDatan);ATLASSERT(0);return pv;_AtlCreateWndData* pEntry = pWinModule-m_pCreateWndList;if(pEntry != NULL)DWORD dwThreadID = :GetCurrentThreadId();_AtlCreateWndData* pPrev = NULL;while(pEntry != NULL)if(pEntry-m_dwThreadID = dwThreadID)if(pPrev = NULL)pWinModule-m_pCreateWndList = pEntry-m_pNext;elsepPrev-m_pNext = pEntry-m_pNext;pv = pEntry-m_pThis;break;pPrev = pEntry;pEntry = pEntry-m_pNext;return pv;这里可以看出,先将头部节点取出来,查看线程ID是否是和当前ID一致。这个很重要。下面就分析一下原因。每一个线程都是按一条指令一条指令去执行,这里的_AtlWinModule保存的可能会有很多线程创建的类似CMainDlg的对象,也依次添加到了头部。所以如果取出来的不是当前线程的ID对应的对象的话,就继续往下找。如果找到了是当前线程的对象,按照指令一条一条往下执行的原则,这个对象绝对是在Create方法中调用_AtlWinModule.AddCreateWndData(&m_thunk.cd, this);这一句话来添加的。因为线程里面的指令都是按条往下执行的,所以按照顺序,Create之后马上就会触发Windows消息,在这中间过程中,全局的链表中的对象顺序是没有被库中的其他地方改变(从本质上来说肯定是可以改变的)。最终获得的效果就是在Create方法中将对象添加到全局链表的头部,在StartWindowProc中取出来,然后将该对象的m_hWnd和刚刚创建的hWnd关联起来。(其实封装HWND的窗口类本质就是要把hWnd的值保存到自己的m_hWnd变量中,同时还要将任何与hWnd相关的消息流入到窗口类中去执行)。pThis-m_hWnd = hWnd;(这句话就是关联之处)。C,下面来讲讲thunk技术回到上面的StartWindowProc中去,关联好了之后还有以下几句关键代码: pThis-m_thunk.Init(pThis-GetWindowProc(), pThis);WNDPROC pProc = pThis-m_thunk.GetWNDPROC();WNDPROC pOldProc = (WNDPROC):SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);return pProc(hWnd, uMsg, wParam, lParam);Thunk代码定义如下:class CWndProcThunkpublic:_AtlCreateWndData cd;CStdCallThunk thunk;BOOL Init(WNDPROC proc, void* pThis)return thunk.Init(DWORD_PTR)proc, pThis);WNDPROC GetWNDPROC()return (WNDPROC)thunk.GetCodeAddress();#pragma pack(push,1)struct _stdcallthunkDWORD m_mov; / mov dword ptr esp+0x4, pThis (esp+0x4 is hWnd)DWORD m_this; /BYTE m_jmp; / jmp WndProcDWORD m_relproc; / relative jmpBOOL Init(DWORD_PTR proc, void* pThis)m_mov = 0x042444C7; /C7 44 24 0Cm_this = PtrToUlong(pThis);m_jmp = 0xe9;m_relproc = DWORD(INT_PTR)proc - (INT_PTR)this+sizeof(_stdcallthunk);/ write block from data cache and/ flush from instruction cacheFlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk);return TRUE;/some thunks will dynamically allocate the memory for the codevoid* GetCodeAddress()return this;void* operator new(size_t) return _AllocStdCallThunk(); void operator delete(void* pThunk) _FreeStdCallThunk(pThunk); ;#pragma pack(pop)这几句代码最重要的作用就是将窗口过程函数修改为pProc,pProc是由pThis-GetWindowProc()返回的,pProc是thunk这段代码的起始位置,这样的效果是什么呢?当触发windows消息的时候,系统会将hwnd,umsg,wparam,lparam压栈,然后调用窗口过程函数,更改之后,就变为触发消息的时候,将参数压栈之后调用thunk的代码。thunk的代码的作用就是将保存在寄存器中的hwnd替换乘this指针,然后jmp到WindowProc,这样在WindowProc中收到的hwnd其实就已经是CMainDlg的指针了。这个产生的效果就是每次windows消息触发之后,就会调用thunk的指令代码,thunk的指令代码将hwnd的值替换为对象this的值,然后调用WindowProc。WindowProc定义如下static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);这样的话,只要CMainDlg的m_hWnd和一个窗口句柄关联好了之后,以后的任何与hWnd相关的消息都会由WindowProc来处理。来看看WindowProc的代码:LRESULT CALLBACK CWindowImplBaseT:WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)CWindowImplBaseT* pThis = (CWindowImplBaseT*)hWnd;/ set a ptr to this message and save the old value_ATL_MSG msg(pThis-m_hWnd, uMsg, wParam, lParam);const _ATL_MSG* pOldMsg = pThis-m_pCurrentMsg;pThis-m_pCurrentMsg = &msg;/ pass to the message map to processLRESULT lRes;BOOL bRet = pThis-ProcessWindowMessage(pThis-m_hWnd, uMsg, wParam, lParam, lRes, 0);/ restore saved value for the current messageATLASSERT(pThis-m_pCurrentMsg = &msg);/ do the default processing if message was not handledif(!bRet)if(uMsg != WM_NCDESTROY)lRes = pThis-DefWindowProc(uMsg, wParam, lParam);else/ unsubclass, if neededLONG_PTR pfnWndProc = :GetWindowLongPtr(pThis-m_hWnd, GWLP_WNDPROC);lRes = pThis-DefWindowProc(uMsg, wParam, lParam);if(pThis-m_pfnSuperWindowProc != :DefWindowProc & :GetWindowLongPtr(pThis-m_hWnd, GWLP_WNDPROC) = pfnWndProc):SetWindowLongPtr(pThis-m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis-m_pfnSuperWindowProc);/ mark window as destryedpThis-m_dwState |= WINSTATE_DESTROYED;if(pThis-m_dwState & WINSTATE_DESTROYED) & pOldMsg= NULL)/ clear out window handleHWND hWndThis = pThis-m_hWnd;pThis-m_hWnd = NULL;pThis-m_dwState &= WINSTATE_DESTROYED;/ clean up after window is destroyedpThis-m_pCurrentMsg = pOldMsg;pThis-OnFinalMessage(hWndThis);else pThis-m_pCurrentMsg = pOldMsg;return lRes;第一句代码:CWindowImplBaseT* pThis = (CWindowImplBaseT*)hWnd;的作用就是从一个hWnd获得一个指向CMainDlg的this指针。因为在调用WindowProc之前已经由thunk中的代码段把保存hwnd的寄存器的值替换成了CMainDlg的指针了,所以这里直接进行强制类型转换,将CMainDlg的指针转换为父类的指针就没有什么奇怪的了。总之结论就是,经过这一系列的过程,窗口的窗口过程函数变成了WindowProc,然后可以在WindowProc中获得与hWnd相关的那个对象也就是CMainDlg对象,获得之后调用该对象的ProcessWindowMessage方法。调用该对象的ProcessWindowMessage方法来处理窗口的各种消息。ProcessWindowMessage是一个虚函数,其定义是通过BEGIN_MSG_MAP和END_MSG_MAP宏来定义的,所以每个单独的编写了这一对宏,就相当于重新定义了ProcessWindowMessage方法。这样的话,每个类就可以处理各自的Windows消息了。3,Message Loop到了ProcessWindowMessage这里就一切都清楚了。来看BEGIN_MSG_MAP的定义如下:#define BEGIN_MSG_MAP(theClass) public: BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论