MFC对于跨模块之间的调用破费心机,设计了一条STATE机制.而且设计的颇为麻烦.费了老鼻子劲终于探究了一二而已.
转一篇分析的比较好的文章.
http://hi.baidu.com/rootlife/blog/item/2f37e354ad8cdc5bd10906be.html
这里补充几点:
1._afxthreadstate 是全局的,至于每个线程中维护一个_afxthreadstate变量,还是进程中维护一个_afxthreadstate变量.这个有待验证.
2. 每个模块会维护一个模块状态.如果是动态链接库的话,模块状态的定义在dllmodule.cpp中,static _AFX_DLL_MODULE_STATE afxModuleState;如果是可执行文件的话,模块状态的定义在afxstate.cpp中,PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState)可以看到是进程唯一的.
3. AfxGetModuleThreadState(),去当前模块状态中的m_thread.可以看一下源代码是
AFX_MODULE_THREAD_STATE* AFXAPI AfxGetModuleThreadState()
{
AFX_MODULE_THREAD_STATE* pResult=AfxGetModuleState()->m_thread.GetData();
ENSURE(pResult != NULL);
return pResult;
}
而AfxGetModuleState()的源代码
AFX_MODULE_STATE* AFXAPI AfxGetModuleState()
{
_AFX_THREAD_STATE* pState = _afxThreadState;
ENSURE(pState);
AFX_MODULE_STATE* pResult;
if (pState->m_pModuleState != NULL)
{
// thread state's module state serves as override
pResult = pState->m_pModuleState;
}
else
{
// otherwise, use global app state
pResult = _afxBaseModuleState.GetData();
}
ENSURE(pResult != NULL);
return pResult;
}
一般是取当前模块的模块状态,如果模块状态为空的话,就取可执行文件的模块状态.一般情况下当前模块的状态是NULL的.也就是说一般取的都是可执行文件的模块状态.
4. AFX_MODULE_THREAD_STATE这个结构体又是什么呢? 从字面上看,是模块线程状态,理解起来从字面看不出啥意思,个人觉得命名命的不好.这个结构体是跟模块状态相关的,里面主要保存了当前的WinThread和各个HANDELMAP,有窗口句柄MAP,资源句柄MAP.截取部分代码如下:
CWinThread* m_pCurrentWinThread;
// list of CFrameWnd objects for thread
CTypedSimpleList
// temporary/permanent map state
DWORD m_nTempMapLock; // if not 0, temp maps locked
CHandleMap* m_pmapHWND;
CHandleMap* m_pmapHMENU;
CHandleMap* m_pmapHDC;
CHandleMap* m_pmapHGDIOBJ;
CHandleMap* m_pmapHIMAGELIST;
AFX_MODULE_THREAD_STATE在继承自CWnd的窗口创建时有重要作用.下面就讲一下CWnd的创建过程.
5. CCmdTarget保存有m_pModuleState成员变量,负责保存其指向的模块状态,这个模块状态的指针在CWnd初始化时有用.
m_pModuleState的初始化是在CCmdTarget构造时.m_pModuleState = AfxGetModuleState();可见,其赋值一般就是_afxBaseModuleState.如果没有特别处理模块状态的操作的情况下.
6. CWnd的创建,CWnd将句柄和WndProc Attatch的过程主要是通过hook的方式.在_AfxCbtFilterHook中完成的.贴一段重要的代码:
AFX_MANAGE_STATE(pWndInit->m_pModuleState);
// the window should not be in the permanent map at this time
ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);
// connect the HWND to pWndInit...
pWndInit->Attach(hWnd);
// allow other subclassing to occur first
pWndInit->PreSubclassWindow();
WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();
ASSERT(pOldWndProc != NULL);
// subclass the window with standard AfxWndProc
WNDPROC afxWndProc = AfxGetAfxWndProc();
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,
(DWORD_PTR)afxWndProc);
ASSERT(oldWndProc != NULL);
if (oldWndProc != afxWndProc)
*pOldWndProc = oldWndProc;
pThreadState->m_pWndInit = NULL;
可见,在attatch的过程中,首先会改变当前的模块状态,改变为CCmdTarget初始化时的模块状态,也就是AfxGetModuleState().
Attch上去,并且改变WndProc为AfxGetAfxWndProc()的返回值.
下面继续分析Attach和AfxGetAfxWndProc()这两个过程.
Attach 的过程主要就是
CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist
ASSERT(pMap != NULL);
pMap->SetPermanent(m_hWnd = hWndNew, this);
在HWNDMap中注册句柄和类之间的映射.而afxMapHWND这个过程,又要见到上面讲过的AFX_MODUEL_THREAD_STATE了.
CHandleMap* PASCAL afxMapHWND(BOOL bCreate)
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
if (pState->m_pmapHWND == NULL && bCreate)
{
BOOL bEnable = AfxEnableMemoryTracking(FALSE);
#ifndef _AFX_PORTABLE
_PNH pnhOldHandler = AfxSetNewHandler(&AfxCriticalNewHandler);
#endif
pState->m_pmapHWND = new CHandleMap(RUNTIME_CLASS(CWnd),
ConstructDestruct
offsetof(CWnd, m_hWnd));
#ifndef _AFX_PORTABLE
AfxSetNewHandler(pnhOldHandler);
#endif
AfxEnableMemoryTracking(bEnable);
}
return pState->m_pmapHWND;
}
返回的是当前的MODULE_THREAD_STATE.而由于在Attach之前改变过当前的模块状态 AFX_MANAGE_STATE(pWndInit->m_pModuleState);所以AfxGetModuleThreadState()返回的就是pWndInit->m_pModuleState.也就是CCmdTarget在初始化时的注册的模块状态.如果HWNDMAP 没有的话,会创建.如果有的话,就直接返回.
Attach讲完了,AfxGetAfxWndProc()呢? 看一下AfxGetAfxWndProc()的代码
WNDPROC AFXAPI AfxGetAfxWndProc()
{
#ifdef _AFXDLL
return AfxGetModuleState()->m_pfnAfxWndProc;
#else
return &AfxWndProc;
#endif
}
如果是dll的话,会返回当前模块状态的WndProc.如果是exe的话,就返回全局的AfxWndProc
一般情况下,当前模块状态一般就是_afxBaseModuleState.而_afxBaseModuleState对应的WndProc是AfxWndProcBase.
LRESULT CALLBACK
AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
AFX_MANAGE_STATE(_afxBaseModuleState.GetData());
return AfxWndProc(hWnd, nMsg, wParam, lParam);
}
AfxWndProcBase的作用很简单,只是在调用全局的AfxWndProc改变一下当前的模块状态.
什么情况下,不是_afxBaseModuleState呢? 在你上层的调用中,当你改变过当前的模块状态的话,返回的就是你改变的模块状态了.如果你是在dll中的话,而对应模块状态的WndProc就变成了AfxWndProcDllStatic
LRESULT CALLBACK
AfxWndProcDllStatic(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
AFX_MANAGE_STATE(&afxModuleState);
return AfxWndProc(hWnd, nMsg, wParam, lParam);
}
类似, AfxWndProcDllStatic也是改变一下当前的模块状态,为本模块的模块状态,然后调用AfxWndProc
7.分析完上面的一推,是不是有点晕了,我在跟进MFC的时候,差不多也快晕了.MFC的东西确实有点乱和难懂了.不过看着这么功能完毕的库,也就是几百个文件,1M而已,个人觉得MFC还是有很多地方值得可取的.
继续分析,从窗口的创建过程来看,HWND和CWnd的映射是保存在CCmdTarget成员变量m_pModuleState中的模块状态的MODULE_THREAD_STATE中的hwndmap中的.
下面分析一下一个比较常见的宏,在参考的链接的那篇文章中有比较详细的描述.
AFX_MANAGE_STATE
AFX_MANAGE_STATE的作用切换到指定的Module State,当出了作用域的时候将Module State恢复到原来的值。是在不同的Module State之中切换
8. 一些常见的诡异问题
dll中,我在一个Cwnd中包含一个模态的对话框成员变量m_modalDlg.
创建m_modalDlg的函数
viod CXXXWnd::CreateModalDlg()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_modalDlg.DoModal()
}
这时候,如果实现m_domalDlg的类,比如CXXXDlg.重载了PreTranslateMessage()这个函数,会发现所有的消息都不会进这个函数.
啥原因呢? 待我来慢慢分析
当初始化m_modalDlg时,当前的模块状态一般应该是_afxBaseModuleState.在DoModal时,也就是Attach时,你改变了模块状态为AfxGetStaticModuleState(),所以你的HWND-CWND的映射是保存在_afxBaseModuleState的模块线程状态中的.
而我们看看模态对话框DoModal的过程,RunModalLoop() - > AfxPumpMessage() 而AfxPumpMessage() 取到消息之后,消息PreTranslateMessage的过程是在当前模块状态下进行的,也就是AfxGetStaticModuleState().
BOOL AfxInternalPreTranslateMessage(MSG* pMsg)
{
// ASSERT_VALID(this);
CWinThread *pThread = AfxGetThread();
if( pThread )
{
// if this is a thread-message, short-circuit this function
if (pMsg->hwnd == NULL && pThread->DispatchThreadMessageEx(pMsg))
return TRUE;
}
// walk from target to main window
CWnd* pMainWnd = AfxGetMainWnd();
if (CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))
return TRUE;
// in case of modeless dialogs, last chance route through main
// window's accelerator table
if (pMainWnd != NULL)
{
CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
if (pWnd->GetTopLevelParent() != pMainWnd)
return pMainWnd->PreTranslateMessage(pMsg);
}
return FALSE; // no special processing
}
可以看一下 CWnd::FromHandle(pMsg->hwnd)这个过程是在当然模块状态下的MODULE_THREAD_STATE下保存的HWNDMAP中查找的,而你HWND-CWND的注册又是在_afxBaseModuleState中.所以当然会查找不到,所以也就不会调用PreTranslateMessage()了,显然你也截获不到任何消息.
终于分析完了.
--by johny