版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、ATL 消息机制的探究作者:后知后觉( 307817387 ) 任何的框架,包括 MFC 或者 ATL ,创建并显示窗口,处理窗口消息都逃不过 RegisterClass 、 CreateWindow 和 Message Loop 。对于 ATL 也是一样的道理, 下面就来细说一下 ATL 的消息处理机制。 重要的部分我会用红色标识出来。1,注册窗口类 CWindowImpl 类使用一个 DECLARE_WND_CLASS(NULL) 的宏来定义 WNDCLASS 的信息 #define DECLARE_WND_CLASS(WndClassName) static ATL:CWndClassI
2、nfo& 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 ,这个是定义的默认的窗
3、口处理函数。先提醒一下, 后面有具体说明。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(&_AtlWinModu
4、le, &_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
5、: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(hWndParen
6、t, rect, szWindowName,dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);这里 Register 的里面的具体实现很复杂,不过其本质就是注册窗口类,具体里面的实现的作用就是 根据 GetWndClassInfo 里面定义的结构体里面的内容生成一个 WNDCLASSEX 的信息,窗口过程函数也 是同理,然后注册成窗口类。同时对传入的 m_pfnSuperWindowProc 赋值,其值就是定 义的 StartWindowProc 。自此,可以的出,创建窗口生成消息之后第一步会到 StartWindowProc 中去执行。 具
7、体 StartWindowProc 是什么呢?其实它是 CWindowImplBaseT 里面定义的一个静态函数:static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);T:GetWndClassInfo 函数就是调用 DECLARE_WND_CLASS 宏定义的方法, 其本质就是返回一个定 义好的 CWndClassInfo 对象。这里在创建窗口之前先调用 Register 方法注册一个窗口类,上面也说到了 过。这里的 T:GetWndClassInfo 和 T:G
8、etWndStyle 这些方法的调用很巧妙,它有利的避开了虚函数机 制同时能够实现动态调用的功效。 T 就是我们自定义的类如 CMainDlg ,CMainDlg 继承自 CWindowImpl , CWindowImpl 继承自 CWindowImplBaseT , CWindowImplBaseT 继承自 CWindowImplRoot ,最终 CWindowImplRoot 继承自 TBase 也就是后面的类中传递进来的 CWindow ,同事也继承了 CMessage 。 CWindow 用来封装 HWND , CMessage 用来封装消息循环。总之 T 就是我们定义的最终的子类 C
9、MainDlg , 所 以 继 承 链 中 任 何 一 个 类 定 义 了 GetWndClassInfo 或 者 GetWndStyle 方 法 , T:GetWndClassInfo 或者 T:GetWndStyle 就可以调用到该方法,这样的话,如果子类要重写该方法,即 使在父类中调用 T:GetWndClassInfo 或 T:GetWndStyle 也是调用子类重写过的版本, 实现“多态” 的效 果。2,创建窗口窗口类注册好了之后调用 CWindowImplBaseT 的 Create 方法:template HWND CWindowImplBaseT:Create(HWND hWn
10、dParent, _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(E
11、RROR_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, MAKEINTATO
12、M(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.AddCreateWnd
13、Data(&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.
14、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
15、.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 _A
16、TL_WIN_MODULE;这里可以看出, _AtlWinModlue 其实保存了一个 _AtlCreateWndData 的指针, 这个指针就是用来构 建一个窗口类链表的头结点。 _AtlWinModule.AddCreateWndData(&m_thunk.cd, this); 的作用就是将 刚刚新建的 this (就是 CMainDlg 的对象的指针)保存到链表的头部。这句话的具体实现是这样的:void AddCreateWndData(_AtlCreateWndData* pData, void* pObject)AtlWinModuleAddCreateWndData(this, p
17、Data, 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)_AtlRaiseEx
18、ception(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(ERRORUnable to lock critical section inAtlWinModuleAddCreateWndDatan);ATLASS
19、ERT(0);return;pData-m_pNext = pWinModule-m_pCreateWndList;pWinModule-m_pCreateWndList = pData;这里的最后两句话可以很清楚的看到,已经将刚刚生成的AtlCreateWndData 节点也就是 m_thunk里面的 cd 值(这里保存的就是当前对象CMainDlg )添加到了全局的 _AtlWinModlue 的链表的头部。对于_AtlWinModule.AddCreateWndData的说明就先到这里,下文将说明保存保存这个指针的意义之所在。回到上面创建的过程里面, 现在已经将目前的 CMainDlg
20、对象的信息保存在了全局链表的头部,之后就使用 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.Get
21、ModuleInstance(), lpCreateParam);在创建的时候会触发 Windows 消息如 WM_NCCREATE , WM_CREATE 等消息,很自然的,这些消StartWindowProc ,它是一息肯定会由窗口过程函数来处理。 在最初的窗口类定义中可以看到入口函数为个静态的成员函数,定义在 CWindowImplBaseT中。此时消息会先由这个函数处理,内部逻辑如下:template LRESULT CALLBACK CWindowImplBaseT:StartWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam, LPARAM l
22、Param)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_WND
23、PROC, (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();先将刚刚保存在链表头部的节点取出来, 其结果就是得到刚刚添加到头
24、部的对象指针。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_csWind
25、owCreate, 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();_AtlCreateW
26、ndData* 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一致。这个很重要。下面就分析一 下
27、原因。 每一个线程都是按一条指令一条指令去执行, 这里的 _AtlWinModule 保存的可能会有很多线程创 建的类似 CMainDlg 的对象,也依次添加到了头部。所以如果取出来的不是当前线程的ID 对应的对象的话,就继续往下找。如果找到了是当前线程的对象,按照指令一条一条往下执行的原则,这个对象绝对是在 Create 方法中调用 _AtlWinModule.AddCreateWndData(&m_thunk.cd, this); 这一句话来添加的。 因为线程里面的指令都是按条往下执行的,所以按照顺序, Create 之后马上就会触发 Windows 消息, 在这 中间过程中,全局的链表中
28、的对象顺序是没有被库中的其他地方改变(从本质上来说肯定是可以改变的) 。 最终获得的效果就是在 Create 方法中将对象添加到全局链表的头部, 在 StartWindowProc 中取出来, 然后 将该对象的 m_hWnd 和刚刚创建的 hWnd 关联起来。(其实封装 HWND 的窗口类本质就是要把 hWnd 的值 保存到自 己的 m_hWnd 变 量 中,同时 还要将 任何与 hWnd 相关的 消息流 入到窗 口类 中去执行) 。 pThis-m_hWnd = hWnd; (这句话就是关联之处) 。C,下面来讲讲 thunk 技术回到上面的 StartWindowProc 中去,关联好了之
29、后还有以下几句关键代码: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;CStdCallThun
30、k 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;DWORD m_this;BYTE m_jmp;DWORD m_relproc;/ mov dword ptr esp+0x4, pThis (esp+0x4 is hWnd) / jmp WndProc/
31、relative jmpBOOL Init(DWORD_PTR proc, void* pThis)m_mov = 0x042444C7;/C7 44 24 0Cm_this = PtrT oUlong(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(), th
32、is, sizeof(_stdcallthunk); return TRUE;/some thunks will dynamically allocate the memory for the code void* GetCodeAddress()return this;void* operator new(size_t)return _AllocStdCallThunk();void operator delete(void* pThunk)_FreeStdCallThunk(pThunk);#pragma pack(pop)这几句代码最重要的作用就是将窗口过程函数修改为pProc ,pPr
33、oc 是由 pThis-GetWindowProc ()返回的, pProc 是 thunk 这段代码的起始位置,这样的效果是什么呢?当触发 windows 消息的时候,系统 会将 hwnd,umsg,wparam,lparam 压栈,然后调用窗口过程函数,更改之后,就变为触发消息的时候,将 参数压栈之后调用 thunk 的代码。 thunk 的代码的作用就是将保存在寄存器中的 hwnd 替换乘 this 指针,然 后 jmp 到 WindowProc ,这样在 WindowProc 中收到的 hwnd 其实就已经是 CMainDlg 的指针了。 这个产生 的效果就是每次 windows 消息
34、触发之后, 就会调用 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 CWindowI
35、mplBaseT: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
36、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
37、 = 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):Se
38、tWindowLongPtr(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 &= WINSTA
39、TE_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 的寄存器的
40、值替换成了 CMainDlg 的指针了,所以 这里直接进行强制类型转换,将 CMainDlg 的指针转换为父类的指针就没有什么奇怪的了。总之结论就是,经过这一系列的过程,窗口的窗口过程函数变成了 WindowProc ,然后可以在 WindowProc 中 获 得 与 hWnd 相 关 的 那 个 对 象 也 就 是 CMainDlg 对 象 , 获 得 之 后 调 用 该 对 象 的 ProcessWindowMessage 方法。调用该对象的 ProcessWindowMessage 方法来处理窗口的各种消息。 ProcessWindowMessage 是一个虚函数,其定义是通过 BEGI
41、N_MSG_MAP 和 END_MSG_MAP 宏来定义 的,所以每个单独的编写了这一对宏,就相当于重新定义了 ProcessWindowMessage 方法。这样的话, 每个类就可以处理各自的 Windows 消息了。3,Message Loop到了 ProcessWindowMessage这里就一切都清楚了。来看 BEGIN_MSG_MAP 的定义如下:#define BEGIN_MSG_MAP(theClass) public: lParam,BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM LRESULT&
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025车祸私了和解协议书
- (2024)人造刚玉项目可行性研究报告写作范本(一)
- 2024秋新沪科版物理八年级上册课件 第六章 熟悉而陌生的力 第1节 力及其描述
- 2023年体外循环管路项目筹资方案
- 平安夜圣诞节介绍活动方案215
- 电工(初级工)模拟习题含答案
- 山东省枣庄市2023-2024学年七年级上学期期末考试数学试卷(含解析)
- 养老院老人生活设施定期检查制度
- 养老院老人安全教育培训制度
- 《家庭心理咨询》课件
- 《预防未成年人犯罪》课件(图文)
- 创新实践(理论)学习通超星期末考试答案章节答案2024年
- 大国外交演讲与辩论智慧树知到期末考试答案章节答案2024年中国石油大学(华东)
- 2024化工园区危险品运输车辆停车场建设规范
- 星期音乐会智慧树知到期末考试答案章节答案2024年同济大学
- MOOC 综合英语-中南大学 中国大学慕课答案
- (2024年)2型糖尿病教学查房学习教案
- 国开学习网电大数据库应用技术第四次形考作业实验答案
- 超星尔雅学习通《当代大学生国家安全教育》章节测试答案
- 水电站压力钢管防腐施工方案
- 加油站罩棚拆除施工方案(完整版)
评论
0/150
提交评论