目录:
ATL7窗口类剖析目录:前言:第一章HWND和CWindow类Create成员函数:使用CWindow类第二章CWindowImpl类ProcessWindowMessage与消息映射宏窗口创建与消息路由窗口风格修改WNDCLASSEX超类化子类化消息链消息转发第三章CAxHostWindow类第四章CAxWindowT类前言:ATL是微软继MFC之后提供的一套C++模板类库,小巧、精妙、效率极高。它的主要作用是为我们编写COM/DOM/COM+程序提供了丰富的支持。但是ATL只能写COM么?我以前只是MFC程序员的时候,一直有此误解。但其实ATL提供了很多类用来帮助编写WIN32窗口程序,可能没有MFC使用的广泛和方便(当然啦,因为ATL本来难度就较一般的C++类库大)。用ATL编写WIN32窗口程序有什么好处?小巧、效率这些好处之外,还有一个我认为非常大的好处,写一个EXE形式的COM服务程序,该程序拥有自己的窗口可以和用户交互。你想象一下,一个友好的窗口程序,同时暴露了一些COM接口使得可以和其他程序跨进程通信,是不是非常的便利呢?使用ATL编写WIN32窗口应用程序你具备以下基础知识,包括WIN32SDK编程能力、C++模板技术、COM编程的能力。要求很高啊,正因为这样,才萌发了写这篇文章的念头。第一章HWND和CWindow类HWND是WINDOWS窗口的灵魂,每个窗口都对应一个HWND变量,称为窗口句柄。我们可以通过HWND向窗口发送消息,让窗口做一些我们想要的动作或者获取窗口的某些信息(比如设置/窗口标题)。CWindow类保存了窗口句柄,并且包装了一些常用的基于窗口句柄的对窗口的操作。CWindow类定义在atlwin.h文件中。CWindow类提供了很多成员变量和函数,有几个比较重要的:HWND m_hWnd;//保存了窗口句柄staticRECT rcDefault;//静态变量,保存了默认的窗口的初始位置和大小_declspec(selectany) RECT CWindow::rcDefault = { CW_USEDEFAULT, CW_USEDEFAULT, 0, 0 };Create成员函数:HWND Create(LPCTSTR lpstrWndClass, HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,DWORD dwStyle = 0, DWORD dwExStyle = 0,_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)throw(){ATLASSERT(m_hWnd == NULL);if(rect.m_lpRect == NULL)rect.m_lpRect = &rcDefault;m_hWnd = ::CreateWindowEx(dwExStyle, lpstrWndClass, 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);returnm_hWnd;}Creat函数第一步,检测窗口是否已经拥有句柄,然后判断;第二步,检测rect参数的变量m_plpRect是否为NULL,rect类型为class_U_RECT{public:_U_RECT(LPRECT lpRect) : m_lpRect(lpRect){ }_U_RECT(RECT& rc) : m_lpRect(&rc){ }LPRECT m_lpRect;};我们可以直接传递一个RECT变量的指针,RECT变量的指针会被用作构造函数的参数创建一个临时的_U_RECT变量,作为参数传递给Create函数。第三步调用CreateWindowEx函数。这是一个WIN32函数。可以指定扩展窗口风格、已注册窗口类名称、窗口标题、窗口风格、窗口位置矩形、父窗口句柄、菜单资源ID、进程实例和创建窗口时可以指定的创建参数。注意,这里的进程实例句柄来自于_AtlBaseModule.GetModuleInstance(),_AtlBaseModule变量声明于atlcore.h文件中:externCAtlBaseModule _AtlBaseModule;CAtlBaseModule的声明也在atlcore.h文件中classCAtlBaseModule :public_ATL_BASE_MODULE{public:staticboolm_bInitFailed;CAtlBaseModule()throw();~CAtlBaseModule()throw();HINSTANCE GetModuleInstance()throw(){returnm_hInst;}HINSTANCE GetResourceInstance()throw(){returnm_hInstResource;}HINSTANCE SetResourceInstance(HINSTANCE hInst)throw(){returnstatic_cast< HINSTANCE >(InterlockedExchangePointer((void)&m_hInstResource, hInst));}boolAddResourceInstance(HINSTANCE hInst)throw();boolRemoveResourceInstance(HINSTANCE hInst)throw();HINSTANCE GetHInstanceAt(inti)throw();};__declspec(selectany)boolCAtlBaseModule::m_bInitFailed =false;externCAtlBaseModule _AtlBaseModule;CAtlBaseModule类用来取代旧版的ATL中的CComModule类。主要作用是保存进程实例句柄和资源句柄,并且是线程安全的。使用CWindow类说了这么多,我们先来写一个例子程序。创建Win32项目CWindow。在stdafx.h中加入代码:#include
CWindowImplRoot类默认的模板参数TBase为CWindow,所以绝大多数情况下,CWindowImpl类派生自CWindow类。而另一个父类CMessageMap类非常简单classATL_NO_VTABLE CMessageMap{public:virtualBOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT& lResult, DWORD dwMsgMapID) = 0;};
只是声明了一个纯虚函数,我们的派生类必须实现ProcessWindowMessage函数,否则我们的派生类将不能实例化。我们要实现的ProcessWindowMessage函数是一个非常类似于WindowProc函数的成员函数,里面有大量的switch/case语句,可以根据不同的消息调用其他成员函数进行处理,为了简化这些工作,ATL提供了BEGIN_MSG_MAP/END_MSG_MAP以及MESSAGE_HANDLER宏帮助我们实现这个函数。如下:BEGIN_MSG_MAP(CMainWindow)COMMAND_ID_HANDLER(IDM_EXIT, OnFileExit)END_MSG_MAP()MESSAGE_HANDLER(msg,func)宏将消息交给指定的函数处理MESSAGE_RANGE_HANDLER(msgFirst,msgLast,func)宏处理一定范围内的窗口消息这里的func函数具有下面的形式:LRESULT MessageHandler(UINT nMsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled)如果消息没有被func函数处理,则会交给缺省窗口过程处理,如果被func处理,同时又想让消息继续流动下去而不是截断,则可以将bHandled设为FALSE。为了方便处理,ATL对WM_COMMAND和WM_NOTIFY消息提供了更方便的宏,WM_COMMAND用于菜单被按下、加速键被按下或者WIN32控件发送通知给父窗口;WM_NOTIFY用于WIN32控件通知父窗口。WM_NOTIFY是用于后来增加的新控件的,因为那时WM_COMMAND消息的WPARAM和LPARAM的所有位都已经用完了。COMMAND_HANDLER(id,code,func)NOTIFY_HANDLER(id,code,func)处理函数原型:LRESULT CommandHandler(WORD wNotifyCode,WORD wID,HWND hWndCtl,BOOL& bHandled)LRESULT NotifyHandler(int idCtrl,LPNMHDR pnmh,BOOL& bHandled)有时候消息处理函数不关心code参数,下面的宏更加方便,比如用于菜单COMMAND_ID_HANDLER(id,func)NOTIFY_ID_HANDLER(id,func)还有其他一些宏COMMAND_RANGE_HANDLER(idFirst,idLast,func)NOTIFY_RANGE_HANDLER(idFirst,idLast,func)COMMAND_CODE_HANDLER(code,func)NOTIFY_CODE_HANDLER(code,func)窗口创建与消息路由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();returnCWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);}T::GetWndClassInfo()函数将返回CWndClassInfo类型,CWndClassInfo定义如下:#defineCWndClassInfo CWndClassInfoWtypedef_ATL_WNDCLASSINFOW CWndClassInfoW;struct_ATL_WNDCLASSINFOW{WNDCLASSEXW m_wc;LPCWSTR m_lpszOrigName;WNDPROC pWndProc;LPCWSTR m_lpszCursorID;BOOL m_bSystemCursor;ATOM m_atom;WCHAR m_szAutoName[5+sizeof(void)CHAR_BIT];ATOM Register(WNDPROC p){returnAtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule,this, p);}};静态成员函数GetWndClassInfo()是通过宏DECLARE_WND_CLASS定义的#defineDECLARE_WND_CLASS(WndClassName) /staticATL::CWndClassInfo& GetWndClassInfo() /{ /staticATL::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("") /}; /returnwc; /}GetWndClassInfo()完成了CWndClassInfo静态变量的初始化工作。非常重要的一点是,将StartWindowProc函数作为窗口过程保存到m_wc. lpfnWndProc中。我们可以看到StartWindowProc函数是CWindowImplBaseT类的静态成员函数:template<classTBase,classTWinTraits>LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){CWindowImplBaseT< TBase, TWinTraits > pThis = (CWindowImplBaseT< TBase, TWinTraits >)_AtlWinModule.ExtractCreateWndData();ATLASSERT(pThis != NULL);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_DEBUG// check if somebody has subclassed us already since we discard itif(pOldProc != StartWindowProc)ATLTRACE(atlTraceWindowing, 0, _T("Subclassing through a hook discarded./n"));#else(pOldProc);// avoid unused warning#endifreturnpProc(hWnd, uMsg, wParam, lParam);}StartWindowProc函数完成了几个重要的工作:1)获取我们的派生类对象的指针,该指针在第一次窗口过程被调用时将保存到ATL的列表_AtlCreateWndData中。2)保存窗口句柄到m_hWnd中。3)初始化m_thunk变量,m_thunk是一个类型,内部保存了一个结构变量struct_stdcallthunk{DWORD m_mov;// mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)DWORD m_this;//BYTE m_jmp;// jmp WndProcDWORD m_relproc;// relative jmpvoidInit(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));}//some thunks will dynamically allocate the memory for the codevoid GetCodeAddress(){returnthis;}};该结构包含了两个汇编指令:move和jmp。有了它的帮助,StartWindowProc函数内部在执行returnpProc(hWnd, uMsg, wParam, lParam);语句之前,就能够不知不觉的在调用栈里将窗口句柄偷换成我们的派生类的指针。同时,由于SetWindowLongPtr将当前窗口过程修改为静态成员函数WindowProc。所以WindowProc函数将被调用,WindowProc函数内部将调用我们的目的地函数ProcessWindowMessage,消息路由完成。我们总结一下消息路有的经过:窗口创建时,将StartWindowProc注册为窗口过程;窗口的第一个消息到来时,ATL将窗口指针保存到全局列表中;第二个窗口消息到来时,StartWindowProc将句柄保存,从全局列表中获得窗口类的指针,同时将WindowProc成员函数指定为窗口过程,并且用thunk技术将调用栈里面的句柄替换成this指针,然后调用WindowProc;WindowProc内部调用ProcessWindowMessage函数,该函数是通过消息映射宏帮助建立的,该函数内部根据不同的消息调用对应的消息映射函数。后续的消息到来时,ATL将直接调用WindowProc函数。最后一个问题是,窗口类注册时需要指定一个名字以便日后引用,CWindowImpl的Create函数第一次被调用时将检测是否创建,如果没有则创建注册窗口类,同时也指定窗口类的名称。注册窗口类的函数是_ATL_WNDCLASSINFOW结构的Register成员函数。Register内部调用了类AtlModuleRegisterWndClassInfoParamW的成员函数static void FormatWindowClassName(PXSTR szBuffer, void unique){#if defined(WIN64) // || or Windows 2000::wsprintfW(szBuffer, L"ATL:%p", unique);#else::wsprintfW(szBuffer, L"ATL:%8.8X", reinterpret_cast
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/40873
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!