淺談MFC中超類化技術的實現 (轉)

amyz發表於2007-10-17
淺談MFC中超類化技術的實現 (轉)[@more@]

// 關鍵詞:
//  面對、超類化、子類化、Superclassing
//  MFC、CWnd::SubclassWindow
//  通用、CMNCTRL
//
// 主題:
//  透過CWnd::SubclassWindow 的分析,淺談MFC中超類化技術的實現
//
//
// 背景
//  我在2002-12月見了mahongxi (烤雞翅膀)(色摸)在CSDN上的一個帖
//  介紹了MFC中窗體的超類化的概念,以下是對我個人回貼的總結
//
// 日誌
//  修改:Panr 2002-12-15 13:30 版式整理,轉帖到CSDN文件中心
//  修改:Panr 2002-12-15 13:30 勘誤
//  原作:Panr 2002-12-13 12:00
//
// 關於“文件中心”
//  在那個帖子裡看到njtu_shiyl(玉晶)提到了文件中心,
//  我就一直在想文件中心在哪?
//  後來再回顧那個帖時,跟著翅膀兄就來到了這兒
//  所以這篇也就順理成章是我的第一次
//  估計我是找對了地方...
//
//


一:超類化概述
在MFC中窗體例項對某個窗體控制程式碼超類化後,提供了這樣兩種能力:
1.我們對該窗體例項成員函式將會直接改變相關窗體控制程式碼對應的窗體
2.系統傳給相關窗體控制程式碼的訊息會先經過該窗體例項的訊息對映

我舉一個例子來說明:
比如我自己寫了一個類叫CSuperEdit(父類為CEdit),在該類中我宣告瞭void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);並在訊息迴圈裡新增了ON_WM_CHAR 一行
現在我只要在對話方塊CProg1Dlg 中宣告CSuperEdit m_edit;然後在CProg1Dlg::OnInitDialog中,新增以下程式碼,就完成了“超類化”:
HWND hWndControl = ::GetDlgItem(pParent->m_hWnd, IDC_EDIT1);
m_edit.SubclassWindow (hWndControl);

這樣超類化處理以後:
當我們呼叫m_edit.SetWindowText("");,後IDC_EDIT1窗體上對應的文字就會改變為""
當在IDC_EDIT1窗體中敲鍵盤時,系統會呼叫我自己寫的CSuperEdit::OnChar函式(而不是原先的CEdit::OnChar)

二:超類化實現的概述
所有的秘密都在CWnd::SubclassWindow 中,讓我們檢視一下它到底做了些什麼吧,以下是函式體(在WINCORE.CPP內):
BOOL CWnd::SubclassWindow(HWND hWnd)
{
  if (!Attach(hWnd))
  return FALSE;

  // allow any other subclassing to occur
  PreSubclassWindow ();

  // now hook into the AFX WndProc
  WNDPROC* lplpfn = GetSuperWndProcAddr();
  WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (D)AfxGetAfxWndProc());
  ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
  return TRUE;
}

結合註釋不難想到PreSubclassWindow 是非功能性的函式,所以我們只要研究兩個函式就可以瞭解CWnd::SubclassWindow 的大概功能 CWnd::Attach和 ::AfxGetAfxWndProc
兩者中當中CWnd::Attach 對應於實現了功能1,即“我們對該窗體例項呼叫成員函式將會直接改變相關窗體控制程式碼對應的窗體”
::AfxGetAfxWndProc函式對應於實現了功能2,即“系統傳給相關窗體控制程式碼的訊息會先經過該窗體例項的訊息對映”

 

三:功能1的實現
CWnd::Attach 的函式體如下(在WINCORE.CPP檔案內):
BOOL CWnd::Attach(HWND hWndNew)
{
  if (hWndNew == NULL)
  return FALSE;

  CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist

  ASSERT(pMap != NULL);
  pMap->SetPermanent(m_hWnd = hWndNew, this);
  return TRUE;
}
最關鍵的是m_hWnd = hWndNew 一句(接觸過的的朋友都知道,windows系統所有窗體操作函式都是把窗體控制程式碼作為一個呼叫引數),顯然只要我把窗體的控制程式碼儲存下來,那我就可以在系統中唯一地指定一個窗體,然後對該窗體進行操作
是的,思路就是這麼簡單。我們現在看到CWnd(別忘了CsuperEdit 是從CWnd繼承的,這裡的CWnd實際就是CsuperEdit )在Attach 函式中把IDC_EDIT1 的控制程式碼儲存在了成員變數m_hWnd 中,那麼實現功能1,自然也就不在話下了

至於CHandleMap::SetPermanent 函式則是用來延長控制程式碼的使用期的,與“超類化”無關,不在此處討論,其具體實現可參考WINHAND_.H檔案

 


四:功能2的實現
四點一:窗體控制程式碼的GWL_WNDPROC屬性
在前面的討論中,我說過功能2是跟::AfxGetAfxWndProc 有關的,該函式的實現是這樣的(也是在WINCORE.CPP檔案中):
WNDPROC AFXAPI AfxGetAfxWndProc()
{
#ifdef _AFXDLL
 return AfxGetModuleState()->m_pfnAfxWndProc;
#else
 return &AfxWndProc;
#endif
}

這是指在DLL中呼叫的話返回AfxGetModuleState()->m_pfnAfxWndProc;否則返回AfxWndProc 函式的地址。於是在一般的可檔案中CWnd::SubclassWindow 為功能2所做的事可以簡化為一行::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)&AfxWndProc);

該函式的作用是把窗體控制程式碼hWnd 的GWL_WNDPROC 屬性設定為AfxWndProc 的地址,那麼現在急需解決的問題是:窗體控制程式碼的GWL_WNDPROC 屬性是幹什麼用的?其實不用我說,大家都猜得到(因為我們是在討論窗體的訊息嘛,而且我也一直在說AfxWndProc是一個函式),它的作用是指定窗體訊息的處理函式
對於該屬性更準確地描述如下:對於發給窗體的所有訊息,Windows將會以該訊息為引數呼叫窗體控制程式碼的GWL_WNDPROC屬性所指定的函式


四點二:被傳遞到MFC環境中
(本節參考了侯捷老師《深入淺出MFC》中“訊息對映與命令傳遞”一章的“兩萬五千里長徵”)
於是功能2可以表述為:AfxWndProc函式是如何找到我為CSuperEdit 類所寫的訊息對映的?還是從函式體出發
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
 // special message which ntifies the window as using AfxWndProc
 if (nMsg == WM_QUERYAFXWNDPROC)
  return 1;

 // all other messages route through message map
 CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
 return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

如上所列::AfxWndProc 整個函式只有四行,顯然它僅僅是包裝了::AfxCallWndProc 函式,只是把hWnd引數包裝成pWnd,然後轉道::AfxCallWndProc。
::AfxCallWndProc該函式才是真正做了一些事的,但其中與訊息傳遞有關直接關係的就一句:
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
 WPARAM wParam = 0, LPARAM lParam = 0)
{
 ...

 // delegate to 's WindowProc
 lResult = pWnd->WindowProc(nMsg, wParam, lParam);

 ...
 return lResult;
}

現在我們已經看到透過::AfxWndProc/::AfxCallWndProc 兩個函式的接力,作業系統中訊息被傳遞到MFC環境中的。
進一步的討論可以把所有的目光都集中到LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

 

四點三:總結
我們看到轉機了:為了實現不同的函式呼叫,OOP(面對物件程式設計)本身提供繼承、虛擬函式之類的許多的方法。MFC正是一種面對物件的語言

現在CsuperEdit 是繼承自CEdit,CEdit 又繼承自CWnd,我們要讓呼叫CsuperEdit::OnChar 也就沒什麼技術難度。比如,可以在CWnd中寫一個響應鍵盤訊息的虛擬函式 virtual void CWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);,並在CWnd::WindowProc 中呼叫OnChar
那麼我只要過載CsuperEdit::OnChar 函式,程式自然而然就會呼叫我寫的函式了

為了減小程式檔案的體積,做了一些工作,它未用virtual 修飾符來修飾所有的函式,而是把“要響應的訊息和相應的響應函式”登記在一張MESSAGE_MAP(稱,訊息對映)裡。
在AFXMSG_.H檔案中ON_WM_CHAR 宏定義被為{WM_CHAR, 0, 0, ... &OnChar},它的作用就是把WM_CHAR和當前類(現在指CsuperEdit)的OnChar函式,填加到了訊息對映的登記表中
既然有了“訊息對映”這樣一張的登記表,對於“讓CWnd在接受到WM_CHAR 訊息時呼叫CsuperEdit::OnChar”的演算法和程式碼,估計你我都能在兩小時內實現,我就不在此處羅嗦了,至於MFC中的相關的程式碼請參考“深入淺出”一書


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-977270/,如需轉載,請註明出處,否則將追究法律責任。

相關文章