深入剖析WTL—WTL框架窗口分析(5)

转载 2007年09月30日 10:54:00
ATL对窗口消息处理函数的封装

在本节开始部分谈到的封装窗口的两个难题,其中第一个问题是怎样解决将窗口函数的消息转发到HWND相对应的类的实例中的相应函数。

下面我们来看一下,ATL采用的是什么办法来实现的。

我们知道每个Windows的窗口类都有一个窗口函数。

LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

在类CWindowImplBaseT中,定义了两个类的静态成员函数。

template <class TBase = CWindow, class TWinTraits = CControlWinTraits>class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >{public:  … …static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam); … …}


它们都是窗口函数。之所以定义为静态成员函数,是因为每个类必须只有一个窗口函数,而且,窗口函数的申明必须是这样的。

在前面介绍的消息处理逻辑过程中,我们知道怎样通过宏生成虚函数ProcessWindowsMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID)。

现在的任务是怎样在窗口函数中把消息传递给某个实例(窗口)的ProcessWindowsMessage()。这是一个难题。窗口函数是类的静态成员函数,因此,它不象类的其它成员函数,参数中没有隐含this指针。

注意,之所以存在这个问题是因为ProcessWindowsMessage()是一个虚函数。而之所以用虚函数是考虑到类的派生及多态性。如果不需要实现窗口类的派生及多态性,是不存在这个问题的。

通常想到的解决办法是根据窗口函数的HWND参数,寻找与其对应的类的实例的指针。然后,通过该指针,调用该实例的消息逻辑处理函数ProcessWindowsMessage()。

这样就要求存储一个全局数组,将HWND和该类的实例的指针一一对应地存放在该数组中。

ATL解决这个问题的方法很巧妙。该方法并不存储这些对应关系,而是使窗口函数接收C++类指针作为参数来替代HWND作为参数。

具体步骤如下:

· 在注册窗口类时,指定一个起始窗口函数。

· 创建窗口类时,将this指针暂时保存在某处。

· Windows在创建该类的窗口时会调用起始窗口函数。它的作用是创建一系列二进制代码(thunk)。这些代码用this指针的物理地址来取代窗口函数的HWND参数,然后跳转到实际的窗口函数中。这是通过改变栈来实现的。

· 然后,用这些代码作为该窗口的窗口函数。这样,每次调用窗口函数时都对参数进行转换。

· 在实际的窗口函数中,只需要将该参数cast为窗口类指针类型。

详细看看ATL的封装代码。

1. 注册窗口类时,指定一个起始窗口函数。

在superclass中,我们分析到窗口注册时,指定的窗口函数是StartWindowProc()。

2. 创建窗口类时,将this指针暂时保存在某处。

template <class TBase, class TWinTraits>HWND CWindowImplBaseT< TBase, TWinTraits >::Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName,DWORD dwStyle, DWORD dwExStyle, UINT nID, ATOM atom, LPVOID lpCreateParam){ATLASSERT(m_hWnd == NULL);if(atom == 0)return NULL;_Module.AddCreateWndData(&m_thunk.cd, this);if(nID == 0 && (dwStyle & WS_CHILD))nID = (UINT)this;HWND hWnd = ::CreateWindowEx(dwExStyle, (LPCTSTR)MAKELONG(atom, 0), szWindowName,dwStyle, rcPos.left, rcPos.top, rcPos.right - rcPos.left,rcPos.bottom - rcPos.top, hWndParent, (HMENU)nID,_Module.GetModuleInstance(), lpCreateParam);ATLASSERT(m_hWnd == hWnd);return hWnd;}


该函数用于创建一个窗口。它做了两件事。第一件就是通过_Module.AddCreateWndData(&m_thunk.cd, this);语句把this指针保存在_Module的某个地方。

第二件事就是创建一个Windows窗口。

3. 一段奇妙的二进制代码

下面我们来看一下一段关键的二进制代码。它的作用是将传递给实际窗口函数的HWND参数用类的实例指针来代替。

ATL定义了一个结构来代表这段代码:

#pragma pack(push,1)struct _WndProcThunk{	DWORD   m_mov;      // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)	DWORD   m_this;     //	BYTE    m_jmp;      // jmp WndProc	DWORD   m_relproc;   // relative jmp};


#pragma pack(pop)

#pragma pack(push,1)的意思是告诉编译器,该结构在内存中每个字段都紧紧挨着。因为它存放的是机器指令。

这段代码包含两条机器指令:

mov dword ptr [esp+4], pThisjmp WndProc


MOV指令将堆栈中的HWND参数(esp+0x4)变成类的实例指针pThis。JMP指令完成一个相对跳转到实际的窗口函数WndProc的任务。注意,此时堆栈中的HWND参数已经变成了pThis,也就是说,WinProc得到的HWND参数实际上是pThis。

上面最关键的问题是计算出jmp WndProc的相对偏移量。

我们看一下ATL是怎样初始化这个结构的。

class CWndProcThunk{public:	union	{		_AtlCreateWndData cd;		_WndProcThunk thunk;	};	void Init(WNDPROC proc, void* pThis)	{		thunk.m_mov = 0x042444C7;  //C7 44 24 0C		thunk.m_this = (DWORD)pThis;		thunk.m_jmp = 0xe9;		thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));		// write block from data cache and		//  flush from instruction cache		FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));	}};


ATL包装了一个类并定义了一个Init()成员函数来设置初始值的。在语句thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk)); 用于把跳转指令的相对地址设置为(int)proc - ((int)this+sizeof(_WndProcThunk))。



上图是该窗口类的实例(对象)内存映象图,图中描述了各个指针及它们的关系。很容易计算出相对地址是(int)proc - ((int)this+sizeof(_WndProcThunk))。

4. StartWindowProc()的作用

template <class TBase, class TWinTraits>LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_Module.ExtractCreateWndData();	ATLASSERT(pThis != NULL);	pThis->m_hWnd = hWnd;	pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);	WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);	WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);#ifdef _DEBUG	// check if somebody has subclassed us already since we discard it	if(pOldProc != StartWindowProc)		ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded./n"));#else	pOldProc;	// avoid unused warning#endif	return pProc(hWnd, uMsg, wParam, lParam);}


该函数做了四件事:

一是调用_Module.ExtractCreateWndData()语句,从保存this指针的地方得到该this指针。

二是调用m_thunk.Init(pThis->GetWindowProc(), pThis)语句初始化thunk代码。

三是将thunk代码设置为该窗口类的窗口函数。

WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);	WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);


这样,以后的消息处理首先调用的是thunk代码。它将HWND参数改为pThis指针,然后跳转到实际的窗口函数WindowProc()。

四是在完成上述工作后,调用上面的窗口函数。

由于StartWindowProc()在创建窗口时被Windows调用。在完成上述任务后它应该继续完成Windows要求完成的任务。因此在这里,就简单地调用实际的窗口函数来处理。

5. WindowProc()窗口函数

下面是该函数的定义:

template <class TBase, class TWinTraits>LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase,TWinTraits >*)hWnd;// set a ptr to this message and save the old valueMSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };const 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);pThis->m_pCurrentMsg = pOldMsg;// do the default processing if message was not handledif(!bRet){	if(uMsg != WM_NCDESTROY)		lRes = pThis->DefWindowProc(uMsg, wParam, lParam);		else		{			// unsubclass, if needed			LONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC);			lRes = pThis->DefWindowProc(uMsg, wParam, lParam);			if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc)				::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc);			// clear out window handle			HWND hWnd = pThis->m_hWnd;			pThis->m_hWnd = NULL;			// clean up after window is destroyed			pThis->OnFinalMessage(hWnd);		}	}	return lRes;}


首先,该函数把hWnd参数cast到一个类的实例指针pThis。

然后调用pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);语句,也就是调用消息逻辑处理,将具体的消息处理事务交给ProcessWindowMessage()。

接下来,如果ProcessWindowMessage()没有对任何消息进行处理,就调用缺省的消息处理。

注意这里处理WM_NCDESTROY的方法。这和subclass有关,最后恢复没有subclass以前的窗口函数。

WTL对subclass的封装


前面讲到过subclass的原理,这里看一下是怎么封装的。

template <class TBase, class TWinTraits>BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd){	ATLASSERT(m_hWnd == NULL);	ATLASSERT(::IsWindow(hWnd));	m_thunk.Init(GetWindowProc(), this);	WNDPROC pProc = (WNDPROC)&(m_thunk.thunk);	WNDPROC pfnWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);	if(pfnWndProc == NULL)		return FALSE;	m_pfnSuperWindowProc = pfnWndProc;	m_hWnd = hWnd;	return TRUE;}


没什么好说的,它的工作就是初始化一段thunk代码,然后替换原先的窗口函数。
 

相关文章推荐

深入剖析WTL—WTL框架窗口分析

WTL的基础是ATL。WTL的框架窗口是ATL窗口类的继承。因此,先介绍一下ATL对Windows窗口的封装。 由第一部分介绍的Windows应用程序可以知道创建窗口和窗口工作的逻辑是: ...
  • zcxin
  • zcxin
  • 2013-10-23 01:42
  • 1332

深入剖析WTL窗口框架

深入剖析WTL 1、  wtl对窗口的封装 2、  消息封装 3、  消息路由   一、Wtl对窗口的封装 一般我们使用WTL创建窗口的时候继承CWindowImpl和CDialog...

深入剖析WTL框架(二)

ATL的消息处理宏 消息映射的目的是实现ProcessWindowMessage()。ProcessWindowMessage()函数是窗口函数的关键逻辑。 一共有三种消息处理宏,分别...

深入剖析WTL框架(一)

WTL的基础是ATL。WTL的框架窗口是ATL窗口类的继承。因此,先介绍一下ATL对Windows窗口的封装。 由第一部分介绍的Windows应用程序可以知道创建窗口和窗口工作的逻辑是: 1 注...

深入剖析WTL框架(五)

ATL对窗口消息处理函数的封装 在本节开始部分谈到的封装窗口的两个难题,其中第一个问题是怎样解决将窗口函数的消息转发到HWND相对应的类的实例中的相应函数。 下面我们来看一下,ATL采用的是...

深入剖析WTL框架(六)

WTL对框架窗口的封装 ATL仅仅是封装了窗口函数和提供了消息映射。实际应用中,需要各种种类的窗口,比如,每个界面线程所对应的框架窗口。WTL正是在ATL基础上,为我们提供了框架窗口和其他各...

深入剖析WTL框架(四)

superclass superclass是一种生成新的窗口类的方法。它的中心思想是依靠现有的窗口类,克隆出另一个窗口类。被克隆的类可以是Windows预定义的窗口类,这些预定义的窗口类有按钮或下...

VC—主框架窗口绘制背景

VC—主框架窗口绘制背景  转自skyremember的专栏使用VC进行项目开发,特别是简单的MIS系统开发中,通常在用户没有进行操作之前显示的是主框架的窗口(用户点击操作菜单项再弹出处理窗口)。因 ...

WTL之dll内嵌窗口代码结构分析

分析一下今天所看代码的结构,以做记录并整理思路;婴儿学步。 WTL之dll内嵌窗口代码结构分析: dll_xxx.def : 导出dll函数。dll_xxx.h    : dll对应类的定...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)