程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> VC++學習:分析MFC中的映射

VC++學習:分析MFC中的映射

編輯:vc教程
 MFC中大量使用了BEGIN_XXX_MAP這樣的宏,以及映射進行查找優化,例如消息映射,OLE命令映射,以及接口等等。每個映射包含一個指向基類的映射的指針。這樣,當一個類需要根據一定的條件查找一個對象時,它會查找本類對象,如果沒有找到,那麼會查找基類,直到根基類。這類查找包含Windows消息,命令,事件和OLE命令的分發,和對象實現的接口的查詢等等。

  下面是函數BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)的部分代碼,演示了如何根據消息的ID查找處理函數。

  const AFX_MSGMAP* pMessageMap; pMessageMap = GetMessageMap();
  UINT iHash; iHash = (LOWORD((DWord_PTR)pMessageMap) ^ message) & (iHashMax-1);
  AfxLockGlobals(CRIT_WINMSGCACHE);
  AFX_MSG_CACHE* pMsgCache; pMsgCache = &_afxMsgCache[iHash];
  const AFX_MSGMAP_ENTRY* lpEntry;
  if (message == pMsgCache->nMsg && pMessageMap == pMsgCache->pMessageMap)
  {
  // cache hit
  lpEntry = pMsgCache->lpEntry;
  AfxUnlockGlobals(CRIT_WINMSGCACHE);
  if (lpEntry == NULL)
  return FALSE;

  // cache hit, and it needs to be handled
  if (message < 0xC000)
  goto LDispatch;
  else
  goto LDispatchRegistered;
  }
  else
  {
  // not in cache, look for it
  pMsgCache->nMsg = message;
  pMsgCache->pMessageMap = pMessageMap;

  #ifdef _AFXDLL
  for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;
  pMessageMap = (*pMessageMap->pfnGetBaseMap)())
  #else
  for (/* pMessageMap already init'ed */; pMessageMap != NULL;
  pMessageMap = pMessageMap->pBaseMap)
  #endif
  {
  // Note: catch not so common but fatal mistake!!
  // BEGIN_MESSAGE_MAP(CMyWnd, CMyWnd)
  #ifdef _AFXDLL
  ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
  #else
  ASSERT(pMessageMap != pMessageMap->pBaseMap);
  #endif

  if (message < 0xC000)
  {
  // constant window message
  if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntrIEs,
  message, 0, 0)) != NULL)
  {
  pMsgCache->lpEntry = lpEntry;
  AfxUnlockGlobals(CRIT_WINMSGCACHE);
  goto LDispatch;
  }
  }
  else
  {
  // registered Windows message
  lpEntry = pMessageMap->lpEntrIEs;
  while ((lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)
  {
  UINT* pnID = (UINT*)(lpEntry->nSig);
  ASSERT(*pnID >= 0xC000 || *pnID == 0);
  // must be successfully registered
  if (*pnID == message)
  {
  pMsgCache->lpEntry = lpEntry;
  AfxUnlockGlobals(CRIT_WINMSGCACHE);
  goto LDispatchRegistered;
  }
  lpEntry++; // keep looking past this one
  }
  }
  }
pMsgCache->lpEntry = NULL;
  AfxUnlockGlobals(CRIT_WINMSGCACHE);
  return FALSE;
  }

  LDispatch:

  注意

  對查找結果的緩存可以提高查找的效率。

  不要被MFC起的名字欺騙了,從數據結構上來說,查找是順序的,而不是使用CMap類使用的散列技術,所以把最常用的映射項放在最前面有助於提高效率。

  使用查找映射的好處是,可以方便地在派生類中擴展和覆蓋映射(例如重新實現IDispatch),而不用重寫/重載查找函數(消息和命令的分發,或者接口的查詢);也可以不使用對資源消耗很大的虛函數表。(盡管如此,CWnd類還是有無數個虛函數,並且不出意外地看到,在MFC6到MFC7的升級中又有增加)

  使用查找映射的壞處麼,當然是理解上的問題和性能上的損失了。

  句柄映射

  MFC在把句柄封裝成對象方面不遺余力,為了保證同一線程內對象<->句柄映射是一對一的,創建了各種各樣的句柄映射,窗口,GDI對象,菜單諸如此類。為了封裝GetDlgItem,SelectObject這樣的API返回的臨時的句柄,MFC還產生臨時的對象<->句柄映射。句柄映射使得GetParentFrame這樣的函數可以實現。

  CFrameWnd* CWnd::GetParentFrame() const
  {
  if (GetSafeHwnd() == NULL) // no Window attached
  return NULL;

  ASSERT_VALID(this);

  CWnd* pParentWnd = GetParent(); // start with one parent up
  while (pParentWnd != NULL)
  {
  if (pParentWnd->IsFrameWnd())
  return (CFrameWnd*)pParentWnd;
  pParentWnd = pParentWnd->GetParent();
  }
  return NULL;
  }

  _AFXWIN_INLINE CWnd* CWnd::GetParent() const
  { ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::GetParent(m_hWnd)); }

  看到了麼,它首先調用API GetParent,然後去本線程的窗口<->句柄映射查找對象指針,然後調用CWnd::IsFrameWnd來決定對象是否是框架。(謝天謝地,這個函數是用虛函數而不是用CObject::IsKindOf,不然又得遍歷一遍運行時類信息)

  在一些經常調用的函數裡面也使用到這個映射

  LRESULT CALLBACK
  AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
  {
  // special message which identifIEs the window as using AfxWndProc
  if (nMsg == WM_QUERYAFXWNDPROC)
  return 1;

  // all other messages route through message map
  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
  ASSERT(pWnd != NULL);
  ASSERT(pWnd->m_hWnd == hWnd);
  if (pWnd == NULL || pWnd->m_hWnd != hWnd)
  return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
  return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
  }

  也就是說,它要遍歷一遍afxMapHWND()返回的對象裡面的永久的句柄映射。而這個函數在每個消息到達的時候都要調用。這是MFC應用程序性能損失的原因之一。

  同樣的,由於這些對象是被線程所擁有的,MFC的這些句柄映射的存儲方式是線程局部存儲  (thread-local-storage ,TLS)。也就是說,對於同一個句柄,句柄映射中相應的對象可以不一致。這在多線程程序中會造成一些問題,參見微軟知識庫文章Q147578 CWnd Derived MFC Objects and Multi-threaded Applications。

  總結

  MFC為了快速和方便地開發作了很多工作,例如上述的兩種映射,但是性能方面有所損失。開發應用程序時,需要在快速方便和性能損失方面的權衡。(話是這麼說,但是根據摩爾定律,再過兩年我的話就成廢話了)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved