vc++窗口的創建過程(MFC消息機制的經典文章)

vc++窗口的創建過程(MFC消息機制的經典文章)
2010年07月30日
  一、什麼是窗口類
  在Windows中運行的程序,大多數都有一個或幾個可以看得見的窗口,而在這些窗口被創建起來之前,操作系統怎麼知道該怎樣創建該窗口,以及用戶操作該窗口的各種消息交給誰處理呢?所以VC在調用Windows的API(CreateWindow或者CreateWindowEx)創建窗口之前,要求程序員必須定義一個窗口類(不是傳統C++意義上的類)來規定所創建該窗口所需要的各種信息,主要包括:窗口的消息處理函數、窗口的風格、圖標、 鼠標、菜單等。其定義如下:
  typedef struct tagWNDCLASSA(注:該結構爲ANSII版本)
  {
  UINT style ;
  WNDPROC lpfnWndProc ;
  int cbClsExtra ;
  int cbWndExtra ;
  HINSTANCE hInstance ;
  HICON hIcon ;
  HCURSOR hCursor ;
  HBRUSH hbrBackground ;
  LPCSTR lpszMenuName ;
  LPCSTR lpszClassName ;
  }WNDCLASSA, * PWNDCLASSA, NEAR * NPWNDCLASSA, FAR * LPWNDCLASSA ;
  style 表示該類窗口的風格,如style = CS_VREDRAW|CS_HREDRAW表示窗口在運動或者調整大小時需要重畫,關於其它風格可在 MSDN中查到。
  lpfnWndProc爲一指針,指向用戶定義的該窗口的消息處理函數。
  cbClsExtra 用於在窗口類結構中保留一定空間,用於存在自己需要的某些信息。
  cbWndExtra用於在Windows內部保存的窗口結構中保留一定空間。
  hInstance 表示創建該窗口的程序的運行實體代號(WinMain的參數之一)。
  hIcon、hCursor、hbrBackground、lpszMenuName分別表示該窗口的圖標、鼠標形狀、背景色以及菜單。
  lpszClassName表示該窗口類別的名稱,即標識該窗口類的標誌。
  從上面可以看出一個窗口類就對應一個WNDCLASSA結構(這裏以ANSII爲例),當程序員將該結構按自己要求填寫完成後,就可以調用RegisterClass(或RegisterClassEx)函數將該類註冊,這樣以後凡是要創建該窗口,只需要以該類名(lpszClassName中指定)爲參數調用CreateWindow,你看多方便呀,真是一舉多得啊!
  總結:但窗口結構註冊(調用RegisterClass(或RegisterClassEx)函數)後,以後凡是要創建該窗口,只需要以該類名(lpszClassName中指定)爲參數調用CreateWindow。
  二、傳統SDK中的窗口類
  既然我們知道了什麼是窗口類,那我們就將它放到一個傳統的SDK程序中,看看是怎樣運行的。 #include
  LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
  int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
  PSTR szCmdLine, int iCmdShow)
  {
  static TCHAR szAppName[] = TEXT ("HelloWin") ;
  WNDCLAS wndclass ;
  wndclass.style = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc = WndProc ;
  wndclass.cbClsExtra = 0 ;
  wndclass.cbWndExtra = 0 ;
  wndclass.hInstance = hInstance ;
  wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
  wndclass.lpszMenuNam = NULL ;
  wndclass.lpszClassName = szAppName ;
  RegisterClass (&wndclass);
  hwnd = CreateWindow( szAppName, // window class name
  TEXT ("The Hello Program"), // window caption
  WS_OVERLAPPEDWINDOW, // window style
  CW_USEDEFAULT, // initial x position
  CW_USEDEFAULT, // initial y position
  CW_USEDEFAULT, // initial x size
  CW_USEDEFAULT, // initial y size
  NULL, // parent window handle
  NULL, // window menu handle
  hInstance, // program instance handle
  NULL) ; // creation parameters
  ShowWindow (hwnd, iCmdShow) ;
  UpdateWindow (hwnd) ;
  while (GetMessage (&msg, NULL, 0, 0))
  {
  TranslateMessage (&msg) ;
  DispatchMessage (&msg) ;
  }
  return msg.wParam ;
  }
  這是一個標準的Windows程序代碼,程序被啓動後,填寫一個窗口類,然後調用RegisterClass將該類註冊,接着創建該窗口,最後顯示窗口和進入消息循環。
  三、MFC中的窗口類
  當你看到這裏,也許你可能會感到奇怪:我在用MFC嚮導做程序時,並沒有進行什麼窗口類的填寫和註冊嗎?是的,你沒有,但是嚮導幫你做了。在展示嚮導是怎麼做的之前,請讓我先介紹一下預先知識。
  在MFC系統中定義了五個默認的窗口類(這裏不包括AFX_WNDCOMMCTLS_REG),分別定義在AFXIMPL.h中: #define AFX_WND_REG (0x0001)
  #define AFX_WNDCONTROLBAR_REG (0x0002)
  #define AFX_WNDMDIFRAME_REG (0x0004)
  #define AFX_WNDFRAMEORVIEW_REG (0x0008)
  #define AFX_WNDDOLECONTROL_REG (0x0020)
  在WINCORE.cpp定義了這些窗口類對應的字符串名稱: const TCHAR _afxWnd[] = AFX_WND;
  const TCHAR _afxWndControlBar[] = AFX_WNDCONTROLBAR;
  const TCHAR _afxWndMDIFrame[] = AFX_WNDMDIFRAME;
  const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW;
  const TCHAR _afxWndOleControl[] = AFX_WNDOLERONTROL;
  在AFXIMPL.h中定義了五個AFX_XXX對應的字符串: #define AFX_WND AFX_WNDCLASS("WND")
  #define AFX_WNDCONTROLBAR AFX_WNDCLASS("ControlBar")
  #define AFX_WNDMDIFRAME AFX_WNDCLASS("MDIFrame")
  #define AFX_WNDFRAMEORVIEW AFX_WNDCLASS("FrameOrView")
  #define AFX_WNDOLECONTROL AFX_WNDCLASS("OleControl")
  看到這裏也許有些心急了,其實上面一堆代碼只是定義了五個默認窗口類的字符串名稱和二進制名稱,具體註冊行爲在全局函數AfxDeferRegisterClass中: #define AfxDeferRegisterClass(fClass) \
  ((afxRegisteredClasses & fClass) ? TRUE:AfxEndDeferRegisterClass(fClass)
  #define afxRegisteredClasses AfxGetModuleState()->m_fRegisteredClasses
  BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)
  {
  WNDCLASS wndCls;
  wndCls.lpfnWndProc = DefWindowProc;
  if(fClass & AFX_WND_REG)
  {
  wndCls.lpszClassName=_afxWnd;
  AfxRegisterClass(&wndCls);
  }else if(fClass & AFX_WNDOLECONTROL_REG)
  {
  wndCls.lpszClassName=_afxWndOleControl;
  AfxRegisterClass(&wndCls);
  }else if(fClass & AFX_WNDCONTROLBAR_REG)
  {
  wndCls.lpszClassName=_afxWndControlBar;
  AfxRegisterClass(&wndCls);
  }else if(fClass & AFX_WNDMDIFRAME_REG)
  {
  RegisterWithIcon(&wndCls,_afxWndMDIFrame,AFX_IDI_M DIFRAME);
  }else if(fClass & AFX_WNDFRAMEORVIEW_REG)
  {
  RegisterWithIcon(&wndCls,_afxWndFrameOrView,AFX_ID I_STD_FRAME);
  }else if(fClass & AFX_WNDCOMMCTLS_REG)
  {
  InitCommonControls();
  }
  }
  從以上例子可以看出, AfxDeferRegisterClass函數用if/else結構實現各種不同窗口的註冊,所所以MFC函數窗口註冊的時候調用AfxDeferRegisterClass函數就可以了。
  從上面的代碼可以看出,AfxDeferRegisterClass函數首先判斷該窗口類是否註冊,如已註冊則直接返回,否則調用AfxEndDeferRegisterClass進行註冊,即註冊要求的默認窗口類。其中RegisterWithIcon和InitCommonControls最終也是轉化爲調用AfxRegisterClass,而AfxRegisterClass函數調用RegisterClass進行註冊,啊,終於看到SDK中的RegisterClass了,看到它總有一種親切感!
  有了上面的知識,我們就可以很容易摸清MFC是怎樣註冊窗口類的了!我們知道Windows上所有看得見的東西,在MFC中都是繼承於CWnd類的,而CWnd類創建窗口的成員函數是Create和CreateEx,由於Create最終是調用CreateEx,所以我們只需要看CreateEx函數就行了: create()-->CreateEx()??CREATESTRUCT
  ?? PreCreateWindow(cs);
  |?? AfxDeferRegisterClass(AFX_WND_REG)
  ?? CreateWindowEx()
  BOOL CWnd::CreateEx(DWORD dwExStyle, LPCSTSTR lpszClassName,
  …… LPVOID lpParam)
  {
  CREATESTRUCT cs;
  cs.dwExStyle = dwExStyle;
  … …
  cs.lpCreateParams = lpParam;
  PreCreateWindow(cs);
  AfxHookWindowCreate(this);
  HWND hWnd=::CreateWindowEx(cs.dwStyle,cs.lpszClass,&hel lip;,cs.lpCreateParams);
  ……
  }
  啊,一看到CreateWindowEx,親切感又來了,這不是和SDK中的CreateWindow一樣嘛,是創建窗口!既然這樣,那麼註冊窗口肯定在該函數之前,會是誰呢?如果你做過一點MFC程序,你就會對將眼光停留PreCreateWindow上。對!就是它了。
  PreCreateWindow函數是CWnd類的一個虛擬函數,提供程序設置待創建窗口的屬性(包括窗口類),這樣繼承於CWnd的類都可以按照自己的要求在PreCreateWindow中設置自己的屬性了,而且我們看到MFC也是這樣做的: BOOL CWnd::PreCreateWindow(CREATESTRUCT &cs)
  {
  if(cs.lpszClass = = NULL)
  {
  AfxDeferRegisterClass(AFX_WND_REG);
  cs.lpszClass = _afxWnd;
  }
  return TRUE;
  }
  BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT &cs)
  {
  if(cs.lpszClass = = NULL)
  {
  AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
  cs.lpszClass = _afxWndFrameOrView;
  }
  return TRUE;
  }
  BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT &cs)
  {
  if(cs.lpszClass = = NULL)
  {
  AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG);
  cs.lpszClass = _afxWndMDIFrame;
  }
  }
  BOOL CMDIChildWnd::PreCreateWindow(CREATESTRUCT &cs)
  {
  return CFrameWnd::PreCreateWindow(cs);
  }
  BOOL CView::PreCreateWindow(CREATESTRUCT &cs)
  {
  if(cs.lpszClass = = NULL)
  {
  AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
  cs.lpszClass = _afxWndFrameOrView;
  }
  }
  就是通過繼承的方法,MFC實現常用類的窗口註冊(代碼並不完全,是從MFC中抽取對我們有意義的一部分代碼)。
  四、在MFC中註冊自己的窗口類
  在MFC中創建一個窗口,就必須是繼承於CWnd類的,這樣你的CMyWnd類自然就有了PreCreateWindow方法。你想註冊有自己個性的窗口類,那麼就在該函數中進行吧。也就是在PreCreateWindow函數中註冊自己的窗口類,然後將窗口類的類名以及待創建窗口的其它屬性(見CREATESTRUCT結構)填寫cs,然後返回系統,供系統創建你的窗口。
  用SDK建立類的過程:
  填寫一個窗口類,然後調用RegisterClass將該類註冊,接着創建該窗口,最後顯示窗口和進入消息循環。
  用MFC建立窗口的過程:
  我們知道Windows上所有看得見的東西,在MFC中都是繼承於CWnd類的,而CWnd類創建窗口的成員函數是Create和CreateEx,由於Create最終是調用CreateEx,所以我們只需要看CreateEx函數就行了:
  create()-->CreateEx()??CREATESTRUCT
  ??PreCreateWindow(cs);
  |?? AfxDeferRegisterClass(AFX_WND_REG)
  ?? AfxHookWindowCreate(this); //爲窗口關聯一個消息處理函數WndProc()
  ?? CreateWindowEx()
  ************************************************** ********************************************
  CWnd::CreateEX中HOOK函數作用
  VC 2009-08-26 20:25 閱讀9 評論0 字號: 大大 中中 小小 用最基本的一句話概述,鉤子函數起了很大作用。故事是這樣的,有些漫長,也需要些耐心。
  MFC中消息分爲3類:
  1. WM_COMMAND:所有的UI組件和加速鍵都會產生這種消息,所有派生於CCmdTarget的類都有能力處理該消息
  2. 標準消息:除WM_COMMAND之外的WM_xx消息都是標準消息,派生於CWnd的類都有能力處理該消息
  3. 控件通知消息:用於子窗口控件向父窗口發送的消息
  在MFC的消息映射表的建立中,通過一組宏,你就可以讓自己的類先於父類處理某些Windows消息,這種行爲很像虛函數,只是我們重載的內容不是虛函數,而是消息。
  推動消息的泵
  第一階段 窗口過程
  在產生一個窗口的時候,會調用CFrameWnd::Create,所有的故事也都從這裏展開。下面的代碼爲了簡潔,去掉了不相關的代碼
  BOOL CFrameWnd::Create(…) {
  // …
  if ( ! CreateEx(…)) {
  // …
  }
  // …
  }
  BOOL CWnd::CreateEx(…) {
  // …
  AfxHookWindowCreate( this );
  HWND hWnd = ::CreateWindowEx(…);
  // …
  }
  void AFXAPI AfxHookWindowCreate(CWnd * pWnd) {
  // …
  if (pThreadState -> m_hHookOldCbtFilter == NULL) {
  pThreadState -> m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
  _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
  // …
  }
  // …
  pThreadState -> m_pWndInit = pWnd;
  }
  這樣,通過AfxHookWindowCreate,在當前線程中安裝了一個鉤子,用來攔截和窗口相關的事件,每當:
  1. 另一個窗口成爲active;
  2. 產生或摧毀一個窗口
  3. Minimize或maximize一個窗口;
  4. 移動或縮放一個窗口;
  5. 完成一個來自系統菜單的命令;
  6. 從系統隊列中取出一個消息;
  時,都會先調用_AfxCbtFilterHook(即每當有一個可能引發消息發生的事件的時候都會調用_AfxCbtFilterHook,然後這個函數對這些消息進行過濾,能夠處理的就交給AfxGetAfxWndProc,不能處理的就交給全局的DefWndProc()函數),接下來看看鉤子函數作了什麼:
  LRESULT CALLBACK
  _AfxCbtFilterHook( int code, WPARAM wParam, LPARAM lParam) {
  // …
  WNDPROC afxWndProc = AfxGetAfxWndProc();
  oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
  // …
  }
  WNDPROC AFXAPI AfxGetAfxWndProc() {
  // …
  return & AfxWndProc;
  }
  這樣,_AfxCbtFilterHook的工作總結起來就是通過窗口子類化,把新建的窗口的窗口過程設置成AfxWndProc。
  到這裏,我們終於找到了窗口過程。
  結論
  CFrameWnd::Create創建窗口調用CWnd::CreateEx
  CWnd::CreateEx調用AfxHookWindowCreate準備爲窗口設置鉤子
  AfxHookWindowCreate調用::SetWindowHookEx爲窗口設置了一個WH_CBT類型的鉤子來過濾消息,並把過濾函數設置成_AfxCbtFilterHook
  _AfxCbtFilterHook通過窗口子類化設置窗口的窗口過程爲AfxWndProc
  這樣,通過::DispatchMessage發送給窗口的消息就會源源不斷地送到AfxWndProc中來,可以想到,AfxWndProc利用MFC的消息映射表,分門別類的對消息進行分流。
  即每當有一個可能引發消息發生的事件的時候都會調用_AfxCbtFilterHook,然後這個函數對這些消息進行過濾,能夠處理的就交給AfxGetAfxWndProc,不能處理的就交給全局的DefWndProc()函數
  OnNcCreate,當CWnd對象第一次被創建時,框架在WM_CREATE消息之前調用這個成員函數。可以修改CREATESTRUCT結構,PreCreateWindow也是可以修改CREATESTRUCT
  結構,他們有什麼區別?
  PreCreateWindow用的比較多,OnNcCreate都用在什麼地方??
  OnNcCreate是響應WM_NCCREATE, 當窗口開始時先創建客戶區,所以先發送WM_NCCREATE消息, 當非客戶區都創建好了,再發送WM_CREATE,去創建窗口客戶區,
  The WM_NCCREATE message is sent prior to the WM_CREATE message when a window is first created.
  意思是說,WM_NCCREATE比WM_CREATE先發給窗口程序,在窗口一創建的時候
  就是說:
  0. call CreateWindow/CreateWindowEx開始
  0.5 PreCreateWindow >的第36條是這樣的Differentiate between inheritance of interface and inheritance of implementation. 看了後你馬上就知道,父類中的非虛擬函數是設計成不被子類改寫的。根據有無virtual關鍵字,我們在排除了SubclassWindow後,也就知道PreCreateWindow和PreSubClassWindow是被設計成可改寫的。接着的問題便是該在什麼時候該寫了。要知道什麼時候該寫,必須知道函數是在什麼時候被調用,還有執行函數的想要達到的目的。我們先看看對這三個函數,MSDN給的解釋:
  PreCreateWindow:
  Called by the framework before the creation of the Windows window
  attached to this CWnd object.
  (譯:在窗口被創建並attach到this指針所指的CWnd對象之前,被framework調用)
  PreSubclassWindow:
  This member function is called by the framework to allow other necessary
  subclassing to occur before the window is subclassed.
  (譯:在window被subclassed之前被framework調用,用來允許其它必要的subclassing發生)
  雖然我已有譯文,但還是讓我對CWnd的attach和窗口的subclass作簡單的解釋吧!要理解attach,我們必須要知道一個C++的CWnd對象和窗口(window)的區別:window就是實在的窗口,而CWnd就是MFC用類對window所進行C++封裝。attach,就是把窗口附加到CWnd對象上操作。附加(attach)完成後,CWnd對象才和窗口發生了聯繫。窗口的subclass是指修改窗口過程的操作,而不是面向對象中的派生子類。
  好了,PreCreateWindow由framework在窗口創建前被調用,函數名也說明了這一點,Pre應該是previous的縮寫,PreSubclassWindow由framework在subclass窗口前調用。 這段話說了等於沒說,你可能還是不知道,什麼時候該改寫哪個函數。羅羅嗦嗦的作者,還是用代碼說話吧!源碼之前,了無祕密(候捷語)。我們就看看MFC中的這三個函數都是這樣實現的吧!
  BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
  LPCTSTR lpszWindowName, DWORD dwStyle,
  int x, int y, int nWidth, int nHeight,
  HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
  {
  // allow modification of several common create parameters
  CREATESTRUCT cs;
  cs.dwExStyle = dwExStyle;
  cs.lpszClass = lpszClassName;
  cs.lpszName = lpszWindowName;
  cs.style = dwStyle;
  cs.x = x;
  cs.y = y;
  cs.cx = nWidth;
  cs.cy = nHeight;
  cs.hwndParent = hWndParent;
  cs.hMenu = nIDorHMenu;
  cs.hInstance = AfxGetInstanceHandle();
  cs.lpCreateParams = lpParam;
  if (!PreCreateWindow(cs))
  {
  PostNcDestroy();
  return FALSE;
  }
  AfxHookWindowCreate(this);
  HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
  cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
  cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
  return TRUE;
  }
  // for child windows
  BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
  {
  if (cs.lpszClass == NULL)
  {
  // make sure the default window class is registered
  VERIFY(AfxDeferRegisterClass(AFX_WND_REG));
  // no WNDCLASS provided - use child window default
  ASSERT(cs.style & WS_CHILD);
  cs.lpszClass = _afxWnd;
  }
  return TRUE;
  }
  CWnd::CreateEx先設定cs(CREATESTRUCT),在調用真正的窗口創建函數::CreateWindowEx之前,調用了CWnd::PreCreateWindow函數,並把參數cs以引用的方式傳遞了進去。而CWnd的PreCreateWindow函數也只是給cs.lpszClass賦值而已。畢竟,窗口創建函數CWnd::CreateEx的諸多參數中,並沒有哪個指定了所要創建窗口的窗口類,而這又是不可缺少的(請參考windows程序設計>>第三章)。所以當你需要修改窗口的大小、風格、窗口所屬的窗口類等cs成員變量時,要改寫PreCreateWindow函數。
  // From VS Install PathVC98MFCSRCWINCORE.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,
  (DWORD)AfxGetAfxWndProc());
  ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
  if (*lplpfn == NULL)
  *lplpfn = oldWndProc; // the first control of that type created
  #ifdef _DEBUG
  else if (*lplpfn != oldWndProc)
  {
  ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);
  }
  #endif
  return TRUE;
  }
  void CWnd::PreSubclassWindow()
  {
  // no default processing
  }
  CWnd::SubclassWindow先調用函數Attach(hWnd)讓CWnd對象和hWnd所指的窗口發生關聯。接着在用::SetWindowLong修改窗口過程(subclass)前,調用了PreSubclassWindow。CWnd::PreSubclassWindow則是什麼都沒有做。
  在CWnd的實現中,除了CWnd::SubclassWindow會調用PreSubclassWindow外,還有一處。上面所列函數CreateEx的代碼,其中調用了一個AfxHookWindowCreate函數,見下面代碼:
  // From VS Install PathVC98MFCSRCWINCORE.CPP
  BOOL CWnd::CreateEx( )
  {
  // allow modification of several common create parameters
  if (!PreCreateWindow(cs))
  {
  PostNcDestroy();
  return FALSE;
  }
  AfxHookWindowCreate(this);
  HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
  cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
  cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
  return TRUE;
  }
  接着察看AfxHookWindowCreate的代碼:
  // From VS Install PathVC98MFCSRCWINCORE.CPP
  void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
  {
  if (pThreadState->m_hHookOldCbtFilter == NULL)
  {
  pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
  _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
  if (pThreadState->m_hHookOldCbtFilter == NULL)
  AfxThrowMemoryException();
  }
  }
  其主要作用的::SetWindowsHookEx函數用於設置一個掛鉤函數(Hook函數)_AfxCbtFilterHook,每當Windows產生一個窗口時(還有許多其它類似,請參考>第9章,563頁),就會調用你設定的Hook函數。
  這樣設定完成後,回到CWnd::CreateEx函數中,執行::CreateWindowEx進行窗口創建,窗口一產生,就會調用上面設定的Hook函數_AfxCbtFilterHook。而正是在_AfxCbtFilterHook中對函數PreSubclassWindow進行了第二次調用。見如下代碼:
  // From VS Install PathVC98MFCSRCWINCORE.CPP
  /**/////////////////////////////////////////////// ///////////////////////////////
  // Window creation hooks
  LRESULT CALLBACK
  _AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
  {
  // connect the HWND to pWndInit
  pWndInit->Attach(hWnd);
  // allow other subclassing to occur first
  pWndInit->PreSubclassWindow();
  {
  // subclass the window with standard AfxWndProc
  oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);
  ASSERT(oldWndProc != NULL);
  *pOldWndProc = oldWndProc;
  }
  }
  也在調用函數SetWindowLong進行窗口subclass前調用了PreSubclassWindow.
  通常情況下窗口是由用戶創建的
  CWnd::Create(..)
  ●在此流程中,MFC提供一個機會"PreCreateWindow()供用戶在創建前作點手腳
  而對於對話框等,窗口是通過subclass方式交給用戶的
  系統讀入對話框模板,建立其中各個子窗口
  然後將各子窗口的 消息處理函數替換成 對應的C++對象 的消息處理函數 (Subclass:子類化,或"接管") ,然後,這個子窗口就會按類中定義的方式來動作了。
  在此過程中,調用的是CWnd:SubclassWindow( HWND hWnd );
  ●在此流程中,MFC提供一個機會"PreSubclassWindow" 供用戶在關聯前作點手腳
  具體來說,如果你定義一個窗口(如CButton派生類CMyButton),然後使用對話框數據交換將一個按鈕與自己的派生類對象關聯,這時候,一些"建立前"的處理就應該寫在"PreSubclassWindow"中。
  如果你用的不是"對話框數據關聯",而是在OnInitDialg中自己創建m_mybtn.Create(...)
  這時候,一些"建立前"的處理就應該寫在
  "PreCreateWindow"中。
  這裏"建立前"的處理包括像那些處理,跟PreCreateWindows()做的一些窗口初始化的工作有什麼不同?
  PreCreateWindows函數中沒有窗口可以用--還沒有創建
  PreSubclassWindow函數中可以對窗口進行操作。
  ******************************
  這些在窗口創建之初就加入了鉤子,能否截獲這些鉤子。
  ------------------以下內容是對上面內容的具體解釋,兩部分必須結合着看--------------------------------------
  MFC的窗口類(如CWnd)與窗口過程。
  Windows是基於事件機制的,任何窗口都能發送和處理消息,每一個窗口都對應着自己的消息處理函數,即通常所說的窗口過程(WindowProc)。窗口過程通常是在WNDCLASSEX的lpfnWndProc變量中指定的,然後調用RegisterClassEx註冊窗口類,lpfnWndProc要求是全局的或是類的靜態成員,而MFC的窗口和類對象是一一對應的,在類中定義的窗口過程(CWnd::WindowProc)並非類的靜態成員,那麼窗口消息是怎樣傳給窗口對象的WindowProc函數去處理的呢?MFC中定義了一個全局的AfxWndProc函數,AfxWndProc是MFC中所有的窗口共用的窗口過程。這裏要注意在AfxEndDeferRegisterClass中註冊窗口類時並沒有把AfxWndProc賦給lpfnWndProc,而是把DefWindowProc賦給了lpfnWndProc。真正把AfxWndProc指定爲窗口過程的是在CWnd::CreateEx函數中,CWnd::CreateEx中先後調用了SetWindowsHookEx、CreateWindowEx和UnhookWindowsHookEx,SetWindowsHookEx安裝了一個WH_CBT類型的鉤子,在調用CreateWindowEx時(在CreateWindowEx返回之前)窗口會發送WM_CREATE、 WM_NCCREATE等消息,鉤子過程CBTProc會在窗口消息WM_CREATE、 WM_NCCREATE等發送前被調用,並提前得到窗口的句柄值。鉤子過程CBTProc的任務是把窗口句柄賦給窗口對象(CWnd::m_hWnd),並調用SetWindowLong把窗口過程替換成AfxWndProc(如是控件還要保留原窗口過程,用CallWindowProc進行默認處理)。在這有人可能會問,爲什麼不在AfxEndDeferRegisterClass中直接指定AfxWndProc呢?當然是有原因的:其一是控件的窗口過程必須用SetWindowLong來替換,其二是消息WM_CREATE、 WM_NCCREATE等是在CreateWindowEx返回前發送的,CWnd::WindowProc在處理這些消息時CWnd::m_hWnd必須是已經被初始化的,這個就是由前面的CBTProc完成的。
  好現在我們只要關注AfxWndProc了。AfxWndProc是如何把消息分配給各個窗口對象的窗口過程的呢?在MFC中有一個全局的映射表(還沒到消息映射,呵呵),這個表是窗口句柄到窗口對象的映射(即通過窗口句柄就能找到窗口對象的地址),找到了窗口對象就可以把消息處理的任務交給CWnd::WindowProc了(調用pWnd- >WindowProc)。
  下面就是消息映射了
  其實這就簡單了,因爲這時只需關注CWnd::WindowProc和消處理函數(如onCreate)了。在MFC中定義了幾個宏:DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP等,其實把這幾個宏換回來就很好理解了。爲了便於理解,我把這些宏簡化一下:
  //
  typedef struct _MSGMAP_ENTRY
  {
  UINT nMessage; //消息
  void (CWnd::*pfn)(); //消息處理函數據
  }MSGMAP_ENTRY;
  DECLARE_MESSAGE_MAP相當於
  static MSGMAP_ENTRY _MessageEntry[]; //定義了一個映射表
  BEGIN_MESSAGE_MAP、END_MESSAGE_MAP和兩者之間的宏相當於
  MSGMAP_ENTRY CWnd::_MessageEntry[] =
  {
  {WM_CREATE, &onCreate}, //第一個消息映射
  {WM_CLOSE, &onClose}, //第二個消息映射
  {0, 0} //消息映射結尾
  };
  CWnd::WindowProc之不過是在_MessageEntry[]查找有沒有定義的消息,如有,則調用相應的處理函數,如沒有則調用CWnd::DefWindowProc
  還想提一下Delphi中的相關處理,Delphi是不是用了同樣的方法呢?答案是否定的,Delphi用匯編語句把類的非靜態成員函數的地址賦給lpfnWndProc,這個也很有意思,當然用C++也可這麼做。 對於傳遞函做個解釋如下:
  AfxWndProc()
  該函數負責接收消息,找到消息所屬的CWnd對象,然後調用AfxCallWndProc
  AfxCallWndProc()
  該函數負責保存消息(保存的內容主要是消息標識符和消息參數)供應用程序以後使用,
  然後調用WindowProc()函數
  WindowProc()
  該函數負責發送消息到OnWndMsg()函數,如果未被處理,則調用DefWindowProc()函數
  OnWndMsg()
  該函數的功能首先按字節對消息進行排序,
  對於WM_COMMAND消息,調用OnCommand()消息響應函數,
  對於WM_NOTIFY消息調用OnNotify()消息響應函數。
  任何被遺漏的消息將是一個窗口消息。
  OnWndMsg()函數搜索類的消息映像,以找到一個能處理任何窗口消息的處理函數。
  如果OnWndMsg()函數不能找到這樣的處理函數的話,則把消息返回到WindowProc()函數,
  由它將消息發送給DefWindowProc()函數
  OnCommand()
  該函數查看這是不是一個控件通知
  (lParam參數不爲NULL,如果lParam參數爲空的話,說明該消息不是控件通知),
  如果它是,OnCommand()函數會試圖將消息映射到製造通知的控件;
  如果他不是一個控件通知(或者如果控件拒絕映射的消息)OnCommand()就會調用OnCmdMsg()函數
  OnNotify()也試圖將消息映射到製造通知的控件;
  如果映射不成功,OnNotify()就調用相同的OnCmdMsg()函數
  OnCmdMsg()
  根據接收消息的類,
  OnCmdMsg()函數將在一個稱爲命令傳遞(Command Routing)的過程中潛在的傳遞命令消息和控件通知。
  例如:如果擁有該窗口的類是一個框架類,
  則命令和通知消息也被傳遞到視圖和文檔類,併爲該類尋找一個消息處理函數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章