ATL消息处理机制参考模板.docx
《ATL消息处理机制参考模板.docx》由会员分享,可在线阅读,更多相关《ATL消息处理机制参考模板.docx(19页珍藏版)》请在冰点文库上搜索。
ATL消息处理机制参考模板
ATL消息机制的探究
作者:
后知后觉(307817387)
任何的框架,包括MFC或者ATL,创建并显示窗口,处理窗口消息都逃不过RegisterClass、CreateWindow和MessageLoop。
对于ATL也是一样的道理,下面就来细说一下ATL的消息处理机制。
重要的部分我会用红色标识出来。
1,注册窗口类
CWindowImpl类使用一个DECLARE_WND_CLASS(NULL)的宏来定义WNDCLASS的信息
#defineDECLARE_WND_CLASS(WndClassName)\
staticATL:
:
CWndClassInfo&GetWndClassInfo()\
{\
staticATL:
:
CWndClassInfowc=\
{\
{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;\
}
这里有一个很重要的信息,那就是StartWindowProc,这个是定义的默认的窗口处理函数。
先提醒一下,后面有具体说明。
CWndClassInfo的定义如下:
struct_ATL_WNDCLASSINFOW
{
WNDCLASSEXWm_wc;
LPCWSTRm_lpszOrigName;
WNDPROCpWndProc;
LPCWSTRm_lpszCursorID;
BOOLm_bSystemCursor;
ATOMm_atom;
WCHARm_szAutoName[5+sizeof(void*)*CHAR_BIT];
ATOMRegister(WNDPROC*p)
{
returnAtlWinModuleRegisterWndClassInfoW(&_AtlWinModule,&_AtlBaseModule,this,p);
}
};
其中的Register方法会注册一个窗口类。
在CWindowImpl的Create方法中:
HWNDCreate(HWNDhWndParent,_U_RECTrect=NULL,LPCTSTRszWindowName=NULL,
DWORDdwStyle=0,DWORDdwExStyle=0,
_U_MENUorIDMenuOrID=0U,LPVOIDlpCreateParam=NULL)
{
if(T:
:
GetWndClassInfo().m_lpszOrigName==NULL)
T:
:
GetWndClassInfo().m_lpszOrigName=GetWndClassName();
ATOMatom=T:
:
GetWndClassInfo().Register(&m_pfnSuperWindowProc);
dwStyle=T:
:
GetWndStyle(dwStyle);
dwExStyle=T:
:
GetWndExStyle(dwExStyle);
//setcaption
if(szWindowName==NULL)
szWindowName=T:
:
GetWndCaption();
returnCWindowImplBaseT:
:
Create(hWndParent,rect,szWindowName,
dwStyle,dwExStyle,MenuOrID,atom,lpCreateParam);
}
这里Register的里面的具体实现很复杂,不过其本质就是注册窗口类,具体里面的实现的作用就是根据GetWndClassInfo里面定义的结构体里面的内容生成一个WNDCLASSEX的信息,窗口过程函数也是同理,然后注册成窗口类。
同时对传入的m_pfnSuperWindowProc赋值,其值就是定义的StartWindowProc。
自此,可以的出,创建窗口生成消息之后第一步会到StartWindowProc中去执行。
具体StartWindowProc是什么呢?
其实它是CWindowImplBaseT里面定义的一个静态函数:
staticLRESULTCALLBACKStartWindowProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam);
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
HWNDCWindowImplBaseT:
:
Create(HWNDhWndParent,_U_RECTrect,LPCTSTRszWindowName,DWORDdwStyle,DWORDdwExStyle,_U_MENUorIDMenuOrID,ATOMatom,LPVOIDlpCreateParam)
{
BOOLresult;
ATLASSUME(m_hWnd==NULL);
//Allocatethethunkstructurehere,wherewecanfailgracefully.
result=m_thunk.Init(NULL,NULL);
if(result==FALSE){
SetLastError(ERROR_OUTOFMEMORY);
returnNULL;
}
if(atom==0)
returnNULL;
_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;
HWNDhWnd=:
:
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);
returnhWnd;
}
A,_AtlWinModule.AddCreateWndData(&m_thunk.cd,this)的说明:
这里有几个重要的地方,第一点就是每一个CWindowImpl保存了一个变量m_thunk,这个变量很重要,它的类型是CWndProcThunk,定义如下:
classCWndProcThunk
{
public:
_AtlCreateWndDatacd;
CStdCallThunkthunk;
BOOLInit(WNDPROCproc,void*pThis)
{
returnthunk.Init((DWORD_PTR)proc,pThis);
}
WNDPROCGetWNDPROC()
{
return(WNDPROC)thunk.GetCodeAddress();
}
};
其中cd的定义如下:
struct_AtlCreateWndData
{
void*m_pThis;
DWORDm_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_MODULE70
{
UINTcbSize;
CComCriticalSectionm_csWindowCreate;
_AtlCreateWndData*m_pCreateWndList;
CSimpleArraym_rgWindowClassAtoms;
};
typedef_ATL_WIN_MODULE70_ATL_WIN_MODULE;
这里可以看出,_AtlWinModlue其实保存了一个_AtlCreateWndData的指针,这个指针就是用来构建一个窗口类链表的头结点。
_AtlWinModule.AddCreateWndData(&m_thunk.cd,this);的作用就是将刚刚新建的this(就是CMainDlg的对象的指针)保存到链表的头部。
这句话的具体实现是这样的:
voidAddCreateWndData(_AtlCreateWndData*pData,void*pObject)
{
AtlWinModuleAddCreateWndData(this,pData,pObject);
}
ATLINLINEATLAPI_(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();
CComCritSecLocklock(pWinModule->m_csWindowCreate,false);
if(FAILED(lock.Lock()))
{
ATLTRACE(atlTraceWindowing,0,_T("ERROR:
UnabletolockcriticalsectioninAtlWinModuleAddCreateWndData\n"));
ATLASSERT(0);
return;
}
pData->m_pNext=pWinModule->m_pCreateWndList;
pWinModule->m_pCreateWndList=pData;
}
这里的最后两句话可以很清楚的看到,已经将刚刚生成的_AtlCreateWndData节点也就是m_thunk里面的cd值(这里保存的就是当前对象CMainDlg)添加到了全局的_AtlWinModlue的链表的头部。
对于_AtlWinModule.AddCreateWndData的说明就先到这里,下文将说明保存保存这个指针的意义之所在。
回到上面创建的过程里面,现在已经将目前的CMainDlg对象的信息保存在了全局链表的头部,之后就使用CreateWindowEx方法来创建真正的窗口了。
HWNDhWnd=:
:
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
LRESULTCALLBACKCWindowImplBaseT:
:
StartWindowProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam)
{
CWindowImplBaseT*pThis=(CWindowImplBaseT*)_AtlWinModule.ExtractCreateWndData();
ATLASSERT(pThis!
=NULL);
if(!
pThis)
{
return0;
}
pThis->m_hWnd=hWnd;
pThis->m_thunk.Init(pThis->GetWindowProc(),pThis);
WNDPROCpProc=pThis->m_thunk.GetWNDPROC();
WNDPROCpOldProc=(WNDPROC):
:
SetWindowLongPtr(hWnd,GWLP_WNDPROC,(LONG_PTR)pProc);
#ifdef_DEBUG
if(pOldProc!
=StartWindowProc)
ATLTRACE(atlTraceWindowing,0,_T("Subclassingthroughahookdiscarded.\n"));
#else
(pOldProc);//avoidunusedwarning
#endif
returnpProc(hWnd,uMsg,wParam,lParam);
}
第一句_AtlWinModule.ExtractCreateWndData();先将刚刚保存在链表头部的节点取出来,其结果就是得到刚刚添加到头部的对象指针。
B,_AtlWinModule.ExtractCreateWndData()的说明
void*ExtractCreateWndData()
{
returnAtlWinModuleExtractCreateWndData(this);
}
ATLINLINEATLAPI_(void*)AtlWinModuleExtractCreateWndData(_ATL_WIN_MODULE*pWinModule)
{
if(pWinModule==NULL)
returnNULL;
void*pv=NULL;
CComCritSecLocklock(pWinModule->m_csWindowCreate,false);
if(FAILED(lock.Lock()))
{
ATLTRACE(atlTraceWindowing,0,_T("ERROR:
UnabletolockcriticalsectioninAtlWinModuleExtractCreateWndData\n"));
ATLASSERT(0);
returnpv;
}
_AtlCreateWndData*pEntry=pWinModule->m_pCreateWndList;
if(pEntry!
=NULL)
{
DWORDdwThreadID=:
:
GetCurrentThreadId();
_AtlCreateWndData*pPrev=NULL;
while(pEntry!
=NULL)
{
if(pEntry->m_dwThreadID==dwThreadID)
{
if(pPrev==NULL)
pWinModule->m_pCreateWndList=pEntry->m_pNext;
else
pPrev->m_pNext=pEntry->m_pNext;
pv=pEntry->m_pThis;
break;
}
pPrev=pEntry;
pEntry=pEntry->m_pNext;
}
}
returnpv;
}
这里可以看出,先将头部节点取出来,查看线程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);
WNDPROCpProc=pThis->m_thunk.GetWNDPROC();
WNDPROCpOldProc=(WNDPROC):
:
SetWindowLongPtr(hWnd,GWLP_WNDPROC,(LONG_PTR)pProc);
returnpProc(hWnd,uMsg,wParam,lParam);
Thunk代码定义如下:
classCWndProcThunk
{
public:
_AtlCreateWndDatacd;
CStdCallThunkthunk;
BOOLInit(WNDPROCproc,void*pThis)
{
returnthunk.Init((DWORD_PTR)proc,pThis);
}
WNDPROCGetWNDPROC()
{
return(WNDPROC)thunk.GetCodeAddress();
}
};
#pragmapack(push,1)
struct_stdcallthunk
{
DWORDm_mov;//movdwordptr[esp+0x4],pThis(esp+0x4ishWnd)
DWORDm_this;//
BYTEm_jmp;//jmpWndProc
DWORDm_relproc;//relativejmp
BOOLInit(DWORD_PTRproc,void*pThis)
{
m_mov=0x042444C7;//C744240C
m_this=PtrToUlong(pThis);
m_jmp=0xe9;
m_rel