zoukankan      html  css  js  c++  java
  • FromHandle临时对象一探究竟

    我们在调用CWnd::GetDlgItem()函数时,MSDN告诉我们:The returned pointer may be temporary and should not be stored for later use.
    中文意思就是:返回的指针可能是临时的并且最好不要保存起来放到以后用。
    猜测:返回的指针既然可能是临时的,那么可能是非临时的(永久的),最好不要保存起来放到以后用(有时候可以保存起来)
    源码面前,了无秘密。让我们深入MFC源代码去看个究竟。
     
    先随便建立一个Dialog程序,然后在窗体上拉一个按钮,添加按钮事件,在按钮事件里写上如下代码:GetDlgItem(IDC_BUTTON1);然后给这行代码加上断点。好,我们开始进去看看,运行程序,按下按钮,程序就停在刚才的断点出,然后F11进去。
    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;
    }
     
    再一次跟踪进入红色函数里去:
    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说临时的map可以被锁定,先记住就可以。
    看一下黄色下面的代码:
       CHandleMap* m_pmapHWND;
       CHandleMap* m_pmapHMENU;
       CHandleMap* m_pmapHDC;
       CHandleMap* m_pmapHGDIOBJ;
       CHandleMap* m_pmapHIMAGELIST;
    很明显,这里放了一些临时的映射,包括HWND到CWnd,HDC到CDC的,等等。
    并且,MFC除了这些临时的映射表之外,还有永久的映射表。
    也就是说,GetDlgItem以及FromHandle等函数返回的CWnd以及CDC等指针是可以保存的,不管是临时的map还是永久 map中的,我们都可以安全的保存,前提是把临时的map锁定,那么不管怎么样,返回的指针可以在任何时候都是安全的。默认的,MFC是在空闲时间里把临 时map里的东西清空掉的。
     
    刚才说到在空闲时间MFC会把临时的map删除掉,我们在刚才那个按钮事件里添上如下代码:
    AfxGetApp()->OnIdle(1);
     
    然后运行并跟踪到如下函数:
    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
    }
     
    再跟踪进上面的红色函数中,整个函数如下:
    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
    }
     
    怎么样,当了老半天的间谍了,总算能看出点门道了吧?上面3行红色的代码说的很清楚,这里会把临时的map释放掉。
    两个函数的代码如下:
    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;
    }
     
    看到上面黄色的代码了吧,这时候你还敢保存GetDlgItem或者FromHandle返回的指针吗?
    刚才你不是说可以锁定临时map的吗?是的,可以用AfxLockTempMaps锁定,但是必须与AfxUnlockTempMaps配对使用,其内部有一个计数变量。MSDN上没这个函数,最好不要用它,否则除了什么问题我可不保证哦J
     
    永久的map存放的应该就是我们显示创建的例如CWnd此类的对象了。
    其实很多函数如FromHandle、FindWindow等函数都是用到了这种技术,所以MSDN必定有这样类似这样的一句话:The returned pointer may be temporary and should not be stored for later use.
  • 相关阅读:
    Mapbox GL JS使用小结(一)
    js 跳转链接的几种方式
    使用iis 部署 .net项目遇到的问题
    ROS 导入示例程序并建立工程运行
    C# WPF程序增加终端串口打印调试信息
    C# 继承方法重写调用测试
    C# 迭代器实现
    C# 引用和值都按照引用传递(其实传递的就是指针)
    C# string引用类型参数不变性
    C# 值类型和引用类型
  • 原文地址:https://www.cnblogs.com/sideny/p/3278902.html
Copyright © 2011-2022 走看看