一个线程中可能(很可能)有不止一个窗口,因此也会有多个对应的CWnd对象。每个CWnd对象只响应发送给本窗口的消息,那么,如何将线程接受到的消息交给不同的CWnd对象呢?
Windows是通过窗口函数将消息发送给应用程序的。窗口函数的第一个参数hWnd指示了接收此消息的窗口,我们只能通过窗口句柄hWnd的值找到对应的CWnd对象的地址。这就要求:
(1)只安排一个窗口函数。窗口函数的作用仅仅是找到处理该消息的CWnd对象的地址,再把它交给此CWnd对象。增加窗口函数对寻找CWnd对象不会有帮助,因为窗口函数的参数是固定的。
(2)记录窗口句柄到CWnd对象指针的映射关系。
窗口函数是全局函数,将它命名为AfxWndProc,其实现代码在CWnd类的实现文件WINCORE.CPP中。假设CWnd类用于接收消息的成员函数的名称是WindowProc,则AfxWndProc的伪代码如下。
LRESULT __stdcall AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
CWnd* pWnd = ... // 通过hWnd找到对应的CWnd指针;
ASSERT(pWnd != NULL);
ASSERT(pWnd->m_hWnd == hWnd);
... // 将消息交给CWnd对象处理return pWnd->WindowProc(nMsg, wParam, lParam);
}
AfxWndProc是程序中所有窗口的消息处理函数,它先找到管理窗口的CWnd对象,再将消息交给该对象处理,并返回消息的处理结果。图6.1显示了此函数的功能。
解决问题(2),只要使用CHandleMap类就可以了。由于Windows为每个线程维护一个消息队列,如图6.1所示,线程1执行过程中消息处理函数AfxWndProc只能收到本线程中的窗口发来的消息,所以窗口的句柄映射应该是线程私有的。CWnd类对象和它所控制的窗口都在同一个模块中,因此窗口句柄映射是模块线程私有的。所以最终我们将记录窗口句柄映射的CHandleMap对象定义在模块线程状态类AFX_MODULE_THREAD_STATE中。
class AFX_MODULE_THREAD_STATE : public CNoTrackObject // _AFXSTAT_.H文件
{
…… // 其他成员
// 窗口句柄映射
CHandleMap* m_pmapHWND;
};

m_pmapHWND指针所指向的CHandleMap对象记录了本模块内当前线程的窗口句柄映射,这里的当前线程是指访问此变量的线程。下面的函数afxMapHWND用于访问当前线程中窗口句柄映射。
CHandleMap* afxMapHWND(BOOL bCreate = FALSE) // 定义在WINCORE.CPP文件
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
if(pState->m_pmapHWND == NULL && bCreate)
{
pState->m_pmapHWND = new CHandleMap();
}
return pState->m_pmapHWND;
}
需要访问当前线程的窗口句柄映射时,只要调用afxMapHWND函数即可。如果仅仅是查询,就将bCreate参数的值设置为FALSE;如果是向映射中添加新项,就要将TRUE传给bCreate参数,此时,afxMapHWND会检查当前线程中的CHandleMap对象是否创建,如果没有就创建它。
CWnd类提供以下4个成员函数来管理窗口句柄映射,这些函数都是先调用afxMapHWND函数得到CHandleMap指针,然后再进行相关操作。
class CWnd : public CCmdTarget // _AFXWIN.H文件
{
…… // 其他成员
static CWnd* FromHandle(HWND hWnd);
static CWnd* FromHandlePermanent(HWND hWnd);
BOOL Attach(HWND hWndNew);
HWND Detach();
}
给定窗口句柄hWnd,FromHandle和FromHandlePermanent函数都会试图返回指向CWnd对象的指针。如果没有CWnd对象附加到此窗口句柄上,FromHandle函数会创建一个临时的CWnd对象,并附加到hWnd上,而FromHandlePermanent函数只返回NULL。但是,我们的CHandleMap类并没有实现自动创建临时对象的功能,所以这两个函数的功能没有区别。函数的实现代码如下。
CWnd* CWnd::FromHandle(HWND hWnd)
{
CHandleMap* pMap = afxMapHWND(TRUE); // 如果不存在则创建一个CHandleMap对象
ASSERT(pMap != NULL);
return (CWnd*)pMap->FromHandle(hWnd);
}
CWnd* CWnd::FromHandlePermanent(HWND hWnd)
{
CHandleMap* pMap = afxMapHWND();
CWnd* pWnd = NULL;
if(pMap != NULL)
{
// 仅仅在永久映射(非临时映射)中查找——不创建任何新的CWnd对象
pWnd = (CWnd*)pMap->LookupPermanent(hWnd);
}
return pWnd;
}
这两个函数的实现不与任何CWnd类的对象有关,而且又是负责查询全局(相对于线程)窗口句柄映射的,所以将它们声明为static类型,做为全局函数来使用。
Attach函数附加一个窗口句柄到当前CWnd对象,即添加一对映射项;Detach函数将窗口句柄从当前CWnd对象分离,即移除一对映射项。这些操作都是在永久映射中进行的,其实现代码如下。
BOOL CWnd::Attach(HWND hWndNew)
{
ASSERT(m_hWnd == NULL); // 仅仅附加一次
ASSERT(FromHandlePermanent(hWndNew) == NULL); // 必须没有在永久映射中
if(hWndNew == NULL)
return FALSE;
CHandleMap* pMap = afxMapHWND(TRUE); // 如果不存在则创建一个CHandleMap对象
ASSERT(pMap != NULL);
pMap->SetPermanent(m_hWnd = hWndNew, this); // 添加一对映射
return TRUE;
}
HWND CWnd::Detach()
{
HWND hWnd = m_hWnd;
if(hWnd != NULL)
{
CHandleMap* pMap = afxMapHWND(); // 如果不存在不去创建
if(pMap != NULL)
pMap->RemoveHandle(hWnd);
m_hWnd = NULL;
}
return hWnd;
}
每创建一个窗口,就调用Attach函数将新的窗口句柄附加到CWnd对象,在此窗口销毁的时候再调用Detach函数取消上面的附加行为。这样,在整个窗口的生命周期内,就会存在一个此窗口句柄hWnd到CWnd对象指针pWnd的映射项,在消息处理函数AfxWndProc中,能够轻易地完成图6.1所示的消息分发的功能,如下代码所示。
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd); // 通过hWnd找到对应的CWnd指针;
return pWnd->WindowProc(nMsg, wParam, lParam); // 将消息交给CWnd对象处理