轉(zhuǎn)自http://blog.csdn.net/loveghb/archive/2005/06/21/399784.aspx
由GetDlgItem函數(shù)想到的
我們?cè)谡{(diào)用CWnd::GetDlgItem()函數(shù)時(shí),MSDN告訴我們:The returned pointer may be temporary and should not be stored for later use.
中文意思就是:返回的指針可能是臨時(shí)的并且最好不要保存起來(lái)放到以后用。
猜測(cè):返回的指針既然可能是臨時(shí)的,那么可能是非臨時(shí)的(永久的),最好不要保存起來(lái)放到以后用(有時(shí)候可以保存起來(lái))
源碼面前,了無(wú)秘密。讓我們深入MFC源代碼去看個(gè)究竟。
先隨便建立一個(gè)Dialog程序,然后在窗體上拉一個(gè)按鈕,添加按鈕事件,在按鈕事件里寫(xiě)上如下代碼:GetDlgItem(IDC_BUTTON1); 然后給這行代碼加上斷點(diǎn)。好,我們開(kāi)始進(jìn)去看看,運(yùn)行程序,按下按鈕,程序就停在剛才的斷點(diǎn)出,然后F11進(jìn)去。
CWnd* CWnd::GetDlgItem(int nID) const
{
ASSERT(::IsWindow(m_hWnd));
if (m_pCtrlCont == NULL)
return CWnd::FromHandle(::GetDlgItem(m_hWnd, nID));
else
return m_pCtrlCont->GetDlgItem(nID);
}
再跟蹤到紅色代碼中去:
CWnd* PASCAL CWnd::FromHandle(HWND hWnd)
{
CHandleMap* pMap = afxMapHWND(TRUE); //create map if not exist
ASSERT(pMap != NULL);
CWnd* pWnd = (CWnd*)pMap->FromHandle(hWnd);
#ifndef _AFX_NO_OCC_SUPPORT
pWnd->AttachControlSite(pMap);
#endif
ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);
return pWnd;
}
再一次跟蹤進(jìn)入紅色函數(shù)里去:
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(CTempWnd),
offsetof(CWnd, m_hWnd));
#ifndef _AFX_PORTABLE
AfxSetNewHandler(pnhOldHandler);
#endif
AfxEnableMemoryTracking(bEnable);
}
return pState->m_pmapHWND;
}
看一下AFX_MODULE_THREAD_STATE的定義:
// AFX_MODULE_THREAD_STATE (local to thread *and* module)
class AFX_MODULE_THREAD_STATE : public CNoTrackObject
{
public:
AFX_MODULE_THREAD_STATE();
virtual ~AFX_MODULE_THREAD_STATE();
// current CWinThread pointer
CWinThread* m_pCurrentWinThread;
// list of CFrameWnd objects for thread
CTypedSimpleList<CFrameWnd*> m_frameList;
// 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;
// thread-local MFC new handler (separate from C-runtime)
_PNH m_pfnNewHandler;
#ifndef _AFX_NO_SOCKET_SUPPORT
// WinSock specific thread state
HWND m_hSocketWindow;
#ifdef _AFXDLL
CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapSocketHandle;
CEmbeddedButActsLikePtr<CMapPtrToPtr> m_pmapDeadSockets;
CEmbeddedButActsLikePtr<CPtrList> m_plistSocketNotifications;
#else
CMapPtrToPtr* m_pmapSocketHandle;
CMapPtrToPtr* m_pmapDeadSockets;
CPtrList* m_plistSocketNotifications;
#endif
#endif
};
看一下黃色的那一行代碼,很明顯,MFC說(shuō)臨時(shí)的map可以被鎖定,先記住就可以。
看一下黃色下面的代碼:
CHandleMap* m_pmapHWND;
CHandleMap* m_pmapHMENU;
CHandleMap* m_pmapHDC;
CHandleMap* m_pmapHGDIOBJ;
CHandleMap* m_pmapHIMAGELIST;
很明顯,這里放了一些臨時(shí)的映射,包括HWND到CWnd,HDC到CDC的,等等。
并且,MFC除了這些臨時(shí)的映射表之外,還有永久的映射表。
也就是說(shuō),GetDlgItem以及FromHandle等函數(shù)返回的CWnd以及CDC等指針是可以保存的,不管是臨時(shí)的map還是永久map中的,我們都可以安全的保存,前提是把臨時(shí)的map鎖定,那么不管怎么樣,返回的指針可以在任何時(shí)候都是安全的。默認(rèn)的,MFC是在空閑時(shí)間里把臨時(shí)map里的東西清空掉的。
剛才說(shuō)到在空閑時(shí)間MFC會(huì)把臨時(shí)的map刪除掉,我們?cè)趧偛拍莻€(gè)按鈕事件里添上如下代碼:
AfxGetApp()->OnIdle(1);
然后運(yùn)行并跟蹤到如下函數(shù):
BOOL CWinApp::OnIdle(LONG lCount)
{
if (lCount <= 0)
{
CWinThread::OnIdle(lCount);
// call doc-template idle hook
POSITION pos = NULL;
if (m_pDocManager != NULL)
pos = m_pDocManager->GetFirstDocTemplatePosition();
while (pos != NULL)
{
CDocTemplate* pTemplate = m_pDocManager->GetNextDocTemplate(pos);
ASSERT_KINDOF(CDocTemplate, pTemplate);
pTemplate->OnIdle();
}
}
else if (lCount == 1)
{
VERIFY(!CWinThread::OnIdle(lCount));
}
return lCount < 1; // more to do if lCount < 1
}
再跟蹤進(jìn)上面的紅色函數(shù)中,整個(gè)函數(shù)如下:
BOOL CWinThread::OnIdle(LONG lCount)
{
ASSERT_VALID(this);
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
// check MFC's allocator (before idle)
if (_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_CHECK_ALWAYS_DF)
ASSERT(AfxCheckMemory());
#endif
if (lCount <= 0)
{
// send WM_IDLEUPDATECMDUI to the main window
CWnd* pMainWnd = m_pMainWnd;
if (pMainWnd != NULL && pMainWnd->m_hWnd != NULL &&
pMainWnd->IsWindowVisible())
{
AfxCallWndProc(pMainWnd, pMainWnd->m_hWnd,
WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);
pMainWnd->SendMessageToDescendants(WM_IDLEUPDATECMDUI,
(WPARAM)TRUE, 0, TRUE, TRUE);
}
// send WM_IDLEUPDATECMDUI to all frame windows
AFX_MODULE_THREAD_STATE* pState = _AFX_CMDTARGET_GETSTATE()->m_thread;
CFrameWnd* pFrameWnd = pState->m_frameList;
while (pFrameWnd != NULL)
{
if (pFrameWnd->m_hWnd != NULL && pFrameWnd != pMainWnd)
{
if (pFrameWnd->m_nShowDelay == SW_HIDE)
pFrameWnd->ShowWindow(pFrameWnd->m_nShowDelay);
if (pFrameWnd->IsWindowVisible() ||
pFrameWnd->m_nShowDelay >= 0)
{
AfxCallWndProc(pFrameWnd, pFrameWnd->m_hWnd,
WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);
pFrameWnd->SendMessageToDescendants(WM_IDLEUPDATECMDUI,
(WPARAM)TRUE, 0, TRUE, TRUE);
}
if (pFrameWnd->m_nShowDelay > SW_HIDE)
pFrameWnd->ShowWindow(pFrameWnd->m_nShowDelay);
pFrameWnd->m_nShowDelay = -1;
}
pFrameWnd = pFrameWnd->m_pNextFrameWnd;
}
}
else if (lCount >= 0)
{
AFX_MODULE_THREAD_STATE* pState = _AFX_CMDTARGET_GETSTATE()->m_thread;
if (pState->m_nTempMapLock == 0)
{
// free temp maps, OLE DLLs, etc.
AfxLockTempMaps();
AfxUnlockTempMaps();
}
}
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
// check MFC's allocator (after idle)
if (_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_CHECK_ALWAYS_DF)
ASSERT(AfxCheckMemory());
#endif
return lCount < 0; // nothing more to do if lCount >= 0
}
怎么樣,當(dāng)了老半天的間諜了,總算能看出點(diǎn)門(mén)道了吧?上面3行紅色的代碼說(shuō)的很清楚,這里會(huì)把臨時(shí)的map釋放掉。
兩個(gè)函數(shù)的代碼如下:
AfxLockTempMaps();
AfxUnlockTempMaps();
void AFXAPI AfxLockTempMaps()
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
++pState->m_nTempMapLock;
}
BOOL AFXAPI AfxUnlockTempMaps(BOOL bDeleteTemps)
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
if (pState->m_nTempMapLock != 0 && --pState->m_nTempMapLock == 0)
{
if (bDeleteTemps)
{
if (bDeleteTemps != -1)
{
// allow COM libraries to be freed
CWinThread* pThread = AfxGetThread();
if (pThread != NULL && pThread->m_lpfnOleTermOrFreeLib != NULL)
(*pThread->m_lpfnOleTermOrFreeLib)(FALSE, FALSE);
}
// clean up temp objects
pState->m_pmapHGDIOBJ->DeleteTemp();
pState->m_pmapHDC->DeleteTemp();
pState->m_pmapHMENU->DeleteTemp();
pState->m_pmapHWND->DeleteTemp();
pState->m_pmapHIMAGELIST->DeleteTemp();
}
#ifndef _AFX_PORTABLE
CWinApp* pApp = AfxGetApp();
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
// restore safety pool after temp objects destroyed
if (pApp != NULL &&
(pThreadState->m_pSafetyPoolBuffer == NULL ||
_msize(pThreadState->m_pSafetyPoolBuffer) < pApp->m_nSafetyPoolSize) &&
pApp->m_nSafetyPoolSize != 0)
{
// attempt to restore the safety pool to its max size
size_t nOldSize = 0;
if (pThreadState->m_pSafetyPoolBuffer != NULL)
{
nOldSize = _msize(pThreadState->m_pSafetyPoolBuffer);
free(pThreadState->m_pSafetyPoolBuffer);
}
// undo handler trap for the following allocation
BOOL bEnable = AfxEnableMemoryTracking(FALSE);
pThreadState->m_pSafetyPoolBuffer = malloc(pApp->m_nSafetyPoolSize);
if (pThreadState->m_pSafetyPoolBuffer == NULL)
{
TRACE1("Warning: failed to reclaim %d bytes for memory safety pool.\n",
pApp->m_nSafetyPoolSize);
// at least get the old buffer back
if (nOldSize != 0)
{
//get it back
pThreadState->m_pSafetyPoolBuffer = malloc(nOldSize);
ASSERT(pThreadState->m_pSafetyPoolBuffer != NULL);
}
}
AfxEnableMemoryTracking(bEnable);
}
#endif // !_AFX_PORTABLE
}
// return TRUE if temp maps still locked
return pState->m_nTempMapLock != 0;
}
看到上面黃色的代碼了吧,這時(shí)候你還敢保存GetDlgItem或者FromHandle返回的指針嗎?
剛才你不是說(shuō)可以鎖定臨時(shí)map的嗎?是的,可以用AfxLockTempMaps鎖定,但是必須與AfxUnlockTempMaps配對(duì)使用,其內(nèi)部有一個(gè)計(jì)數(shù)變量。MSDN上沒(méi)這個(gè)函數(shù),最好不要用它,否則除了什么問(wèn)題我可不保證哦 J
永久的map存放的應(yīng)該就是我們顯示創(chuàng)建的例如CWnd此類(lèi)的對(duì)象了。
其實(shí)很多函數(shù)如FromHandle、FindWindow等函數(shù)都是用到了這種技術(shù),所以MSDN必定有這樣類(lèi)似這樣的一句話:The returned pointer may be temporary and should not be stored for later use.
有興趣大家自己跟蹤代碼,源碼面前了無(wú)秘密
溫柔的毒藥 2005-06-21
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處
聯(lián)系客服