天天看點

MFC學習筆記

1 概述

本文檔是在學習<<深入淺出>>MFC過程中臨時寫的一篇文檔,并沒有真正的深入。本文檔結合VC9單文檔程式來寫的。

2 MFC六大技術

對應<<深入淺出MFC>>的第三章。

2.1 MFC程式的初始化過程

2.1.1 _tWinMain

_tWinMain(…){    

return AfxWinMain(…);    

}   

從代碼可以看到,該函數僅調用了AfxMain函數。

_tWinMain是被C/C++運作期函數調用的,這裡暫不多講,因為這部分很複雜。

2.1.2 AfxWinMain

看一下該函數代碼,這裡僅寫主要的調用,将其它都省略掉。

int AFXAPI AfxWinMain(…){  

CWinThread* pThread = AfxGetThread();  

CWinApp* pApp = AfxGetApp();  

AfxWinInit(…);  

pApp->InitApplication();  

if (!pThread->InitInstance()){  

   pThread->m_pMainWnd->DestroyWindow();  

   nReturnCode = pThread->ExitInstance();  

   goto InitFailure;  

}  

nReturnCode = pThread->Run();  

InitFailure:  

#ifdef _DEBUG  

// Check for missing AfxLockTempMap calls  

if (AfxGetModuleThreadState()->m_nTempMapLock != 0)  

{  

   TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",  

    AfxGetModuleThreadState()->m_nTempMapLock);  

AfxLockTempMaps();  

AfxUnlockTempMaps(-1);  

#endif  

AfxWinTerm();   

2.1.3 AfxGetThread

    這個函數傳回一個CWinThread類型指針,這實際上是一個全局對象的指針,該函數内部執行非常複雜,這裡不講。但可以知道,程式正式通過該指針管理線程的。

2.1.4 AfxGetApp

    傳回CWinApp對象指針,這也是一個全局對象指針,有理由相信,該對象就是theApp這個全局對象。

2.1.5 AfxWinInit

看一下該函數簡化的代碼:

// handle critical errors and avoid Windows message boxes

SetErrorMode(…);

// set resource handles

AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

pModuleState->m_hCurrentInstanceHandle = hInstance;

pModuleState->m_hCurrentResourceHandle = hInstance;

pModuleState->CreateActivationContext();

// fill in the initial state for the application

CWinApp* pApp = AfxGetApp();

// Windows specific initialization (not done if no CWinApp)

pApp->m_hInstance = hInstance;

App->m_lpCmdLine = lpCmdLine;

pApp->m_nCmdShow = nCmdShow;

pApp->SetCurrentHandles();

// initialize thread specific data (for main thread)

AfxInitThread();

// Initialize CWnd::m_pfnNotifyWinEvent

HMODULE hModule = ::GetModuleHandle(_T("user32.dll"));

CWnd::m_pfnNotifyWinEvent = 

(CWnd::PFNNOTIFYWINEVENT)::GetProcAddress(hModule, NotifyWinEvent");

從這個函數可以看出,該函數做了如下工作:

 設定錯誤模式。

 設定資源,這裡先不做深入學習,這裡很可能設定一個子產品句柄,然後程式預設從該子產品中讀取資源。

 初始化CWinApp某些成員。

 AfxInitThread為主線程安裝了一個WH_MSGFILTER類型的挂鈎,并指定了一個挂鈎函數_AfxMsgFilterHook,當消息來自于dialog box, message box, menu, or scroll bar時,系統就會調用挂鈎函數,這裡暫不研究挂鈎函數。

 初始化類的CWnd靜态函數指針m_pfnNotifyWinEvent,它指向子產品user32.dll中的函數NotifyWinEvent,關于該函數參看msdn。

2.1.6 InitApplication

m_pDocManager = CDocManager::pStaticDocManager;

m_pDocManager->AddDocTemplate(NULL);

LoadSysPolicies();

該函數主要初始化了成員m_pDocManager。

LoadSysPolicies函數暫時不深入學習。

2.1.7 InitInstance

這個函數就不多說了。

2.1.8 Run

下面是該函數的代碼:

_AFX_THREAD_STATE* pState = AfxGetThreadState();

// for tracking the idle time state

BOOL bIdle = TRUE;

LONG lIdleCount = 0;

// acquire and dispatch messages until a WM_QUIT message is received.

for (;;)

{

   // phase1: check to see if we can do idle work

   while (bIdle &&

    !::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))

   {

    // call OnIdle while in bIdle state

    if (!OnIdle(lIdleCount++))

     bIdle = FALSE; // assume "no idle" state

   }

   // phase2: pump messages while available

   do

    // pump message, but quit on WM_QUIT

    if (!PumpMessage())

     return ExitInstance();

    // reset "no idle" state after pumping "normal" message

    //if (IsIdleMessage(&m_msgCur))

    if (IsIdleMessage(&(pState->m_msgCur)))

    {

     bIdle = TRUE;

     lIdleCount = 0;

    }

   } while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));

}

Run函數中的for是一個死循環,直到do…while循環中執行ExitInstance為止,這裡不讨論這一點。

如果While循環要退出,必須滿足兩個條件之一:

 PeekMessage線上程的消息循環中檢查到了消息,取出,但不會見該消息從消息隊列中删除。

 OnIdle傳回0值,這意味着沒有空閑時間了,程式要做事了。

可見,這兩個條件是不相同的,從這兩個條件出發,還可以得出如下結論:

 盡管PeekMessage函數沒有從線程消息隊列中取出任何消息,但是也沒有空閑時間了。

 盡管有空閑時間,但PeekMessage從線程消息隊列中取到了消息。

這兩個解釋說明,空閑時間同線程消息隊列為空沒有必要的聯系。

不過,一旦進入do…while循環,執行過PumpMessage函數之後,有一個函數調用IsIdleMessage,用以判斷剛分發的那個消息是否可以讓程式處于空閑時間。在do…while循環結束之前,如果所有消息都不讓程式處于空閑時間,那麼函數OnIdle就不能執行,然而隻要有一個,OnIdle函數酒可以執行(事實上這樣的消息出現頻率很大)。但為何要這樣設計呢?

最後,受到WM_QUIT消息時調用CWinApp::ExitInstance()做一些清理工作。

2.1.9 PumpMessage

看一下該函數所作的事情:

_AFX_THREAD_STATE *pState = AfxGetThreadState();

if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))

   ::TranslateMessage(&(pState->m_msgCur));

   ::DispatchMessage(&(pState->m_msgCur));

調用了GetMessage(通過其它函數),該函數不但從消息隊列取消息,之後還将消息删除。然後配置設定該消息。

2.1.10 AfxWinMain函數InitFailure後的代碼

InitFailure之後的代碼現在暫不考慮。

2.2 RTTI(運作時識别類)

運作時識類技術主要是在類中添加一個結構變量和一些成員函數,通過對象的指針可以找到對象的類名。

在MFC類庫中,将該結構用連結清單連起來,就構成一個類别型錄網,因為結構中含有類名,父類等資訊,是以可以周遊連結清單尋找到本類或父類的資訊(至少有名稱),以此可以識别類。

2.2.1 CRuntimeClass定義

這裡,取未定義_AFXDLL宏。

struct CRuntimeClass

// Attributes

LPCSTR m_lpszClassName;

int m_nObjectSize;

UINT m_wSchema; // schema number of the loaded class

CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class

CRuntimeClass* m_pBaseClass;

// Operations

CObject* CreateObject();

BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

// dynamic name lookup and creation

static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);

static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);

static CObject* PASCAL CreateObject(LPCSTR lpszClassName);

static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);

// Implementation

void Store(CArchive& ar) const;

static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);

// CRuntimeClass objects linked together in simple list

CRuntimeClass* m_pNextClass;       // linked list of registered classes

const AFX_CLASSINIT* m_pClassInit;

};

這該結構聲明,下面會逐一說明每個成員。

2.2.2 DECLARE_DYNAMIC

這個宏用于在一個類中添加同RTTI相關的成員,下面看這個宏的定義。

#define DECLARE_DYNAMIC(class_name) \

public: \

static const CRuntimeClass class##class_name; \

virtual CRuntimeClass* GetRuntimeClass() const; \

以DECLARE_DYNAMIC(CView) 為例,展開後得到:

class CView{

public:

static const CRuntimeClass classCView;//靜态變量

virtual CRuntimeClass* GetRuntimeClass() const;//虛函數聲明

… …

下面看看宏IMPLEMENT_DYNAMIC的實作。

2.2.3 IMPLEMENT_DYNAMIC

這個宏是對DECLARE_DYNAMIC聲明内容的實作,下面是定義:

#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \

IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL)

#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \

AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \

   #class_name, sizeof(class class_name), wSchema, pfnNew, \

    RUNTIME_CLASS(base_class_name), NULL, class_init }; \

CRuntimeClass* class_name::GetRuntimeClass() const \

   { return RUNTIME_CLASS(class_name); }

#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)

#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

以IMPLEMENT_DYNAMIC(CView, CWnd)為例,展開後如下:

AFX_COMDAT const CRuntimeClass CView::classCView = //初始化靜态變量

"CView",         // m_lpszClassName

sizeof(class CView),       // m_nObjectSize

0xFFFF,         // m_wSchema

NULL,         // m_pfnCreateObject

((CRuntimeClass*)(&CWnd::classCWnd), // m_pBaseClass

NULL,         // m_pNextClass

NULL         // m_pClassInit

CRuntimeClass* CView::GetRuntimeClass() const 

return ((CRuntimeClass*)(&CView::classCView);

2.2.4 IsKindOf和IsDerivedFrom

該函數定義在CObject類中,看一下它的定義(簡化版):

BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const

// simple SI case

CRuntimeClass* pClassThis = GetRuntimeClass();

ENSURE(pClassThis);

return pClassThis->IsDerivedFrom(pClass);

下面是該函數的一般調用:

pView->IsKindOf(RUNTIME_CLASS(CView));

注意以下參數,該參數展開後如下:

((CRuntimeClass*)(&CView::classCView))

CView::class CView正是CView類的靜态變量。

調用:

GetRuntimeClass從CObject繼承下來的,它傳回CView::class CView。

有意思的調用是pClassThis->IsDerivedFrom(pClass);

看看IsDerivedFrom的簡化代碼:

BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const

const CRuntimeClass* pClassThis = this;

while (pClassThis != NULL)

   if (pClassThis == pBaseClass)

    return TRUE;

   pClassThis = pClassThis->m_pBaseClass;

return FALSE;       // walked to the top, no match

很顯然,pClassThis就是目前對象所屬類的CRuntimeClass靜态成員的指針,pBaseClass就是參考類(目前對象是否屬于該類)的CRuntimeClass靜态成員的指針,通過這兩個指針對比(是否相同),來判斷對象是否屬于參考類的對象。那麼為何不通過字元串對比(要知道CRuntimeClass有類的名稱字元串)呢?

從while循環可以看出,pClassThis會順着類型連結清單向繼承類方向周遊,這說明一個對象也屬于其繼承類類型,而這一點是無法通過類名稱字元串比較做到的。

2.3 (Dynamic Create)動态建立

盡管能夠在運作時得到一個類的名稱,但還是無法通過該名稱動态建立類的對象,必須再向其它辦法。為此,就要利用CRuntimeClass的m_pfnCreateObject成員,下面是該成員的定義(參考RTTI):

CObject* (PASCAL* m_pfnCreateObject)();

可見,這是一個函數指針,它傳回CObject*。

如果想一個類具有動态建立對象的功能,隻需要實作一個函數,讓該函數建立對象,并且讓m_pfnCreateObject指向該函數即可。

DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏就是用來做這件事情的。

2.3.1 DECLARE_DYNCREATE

看一下該宏的定義:

#define DECLARE_DYNCREATE(class_name) \

DECLARE_DYNAMIC(class_name) \

static CObject* PASCAL CreateObject();

DECLARE_DYNAMIC參考RTTI部分。

可見,該宏也就是為類添加一個靜态成員CreateObject();

如DECLARE_DYNCREATE(CWnd)展開後如下:

class CWnd{

static CObject* PASCAL CreateObject();//靜态成員函數

2.3.2 IMPLEMENT_DYNCREATE

#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \

CObject* PASCAL class_name::CreateObject() \

   { return new class_name; } \

IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \

   class_name::CreateObject, NULL)

IMPLEMENT_RUNTIMECLASS參考RTTI的DECLARE_DYNAMIC。

如IMPLEMENT_DYNCREATE(CWnd, CCmdTarget)展開後如下:

AFX_COMDAT const CRuntimeClass CWnd::classCWnd = //初始化靜态變量

"CWnd",         // m_lpszClassName

sizeof(class CWnd),       // m_nObjectSize

0xFFFF,         // m_wSchema

CWnd::CreateObject,      // m_pfnCreateObject

((CRuntimeClass*)(&CWnd::classCWnd), // m_pBaseClass

NULL,         // m_pNextClass

NULL         // m_pClassInit

CRuntimeClass* CWnd::GetRuntimeClass() const 

return ((CRuntimeClass*)(&CWnd::classCWnd);

CObject* PASCAL CWnd::CreateObject() { return new CWnd;}

從這個宏也可以看出,擁有動态建立能力的類也一定有動态識别能力。

下面再看一下CRuntimeClass的實作(簡化):

CObject* CRuntimeClass::CreateObject()

if (m_pfnCreateObject == NULL)

   return NULL;

CObject* pObject = NULL;

TRY

   pObject = (*m_pfnCreateObject)();

END_TRY

return pObject;

是以,隻要能夠找到一個類的CRuntimeClass靜态成員,就可以動态建立該類的對象,當然,要找到該成員是不費吹灰之力的。

2.3.3 使用

例如動态建立一視窗對象:

CRuntimeClass* prc = RUNTIME_CLASS(CWnd);

CWnd* pWnd = (CWnd*)prc->CreateObject();

BOOL b = pWnd->IsKindOf(RUNTIME_CLASS(CWnd));

delete pWnd;

2.4 Persistence(永久儲存機制)

暫時不看。

2.5 Serialize(資料讀寫)

2.6 Message Mapping(消息映射)

對于同消息處理有關的類,MFC為其建立一個消息映射表,當有消息傳入時,通過查找消息映射表就可以找到對應的函數來處理。

消息映射表其實就是類的一個消息處理成員,如果它的基類也同消息有關,則這個成員的某個子段指向基類的,???

2.6.1 DECLARE_MESSAGE_MAP

這是一個宏,于消息有關的類都有這個宏,下面是該宏的定義:

#define DECLARE_MESSAGE_MAP() \

protected: \

static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \

virtual const AFX_MSGMAP* GetMessageMap() const; \

例如在CView類中展開該宏,效果如下:

class CView : public CWnd

//DECLARE_MESSAGE_MAP()

static const AFX_MSGMAP* PASCAL GetThisMessageMap(); 

virtual const AFX_MSGMAP* GetMessageMap() const; 

可以看出,該宏實際上為CView類添加了兩個函數,一個是靜态函數,另一個是虛函數。

2.6.2 BEGIN…ON…END宏

在定義類的成員時,也要對DECLARE_MESSAGE_MAP()添加的函數進行實作,這兩個宏就起這個作用,下面是這兩個宏的定義:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \

const AFX_MSGMAP* theClass::GetMessageMap() const \

   { return GetThisMessageMap(); } \

const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \

{ \

   typedef theClass ThisClass;         \

   typedef baseClass TheBaseClass;        \

   static const AFX_MSGMAP_ENTRY _messageEntries[] = \

#define END_MESSAGE_MAP() \

   {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \

}; \

   static const AFX_MSGMAP messageMap = \

   { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \

   return &messageMap; \

可以看出,第一個參數theClass就是目前類的名稱,第二個參數baseClass可能是基類的名稱,但不一定(例如CWinApp)。

同樣還是以CView為例,看一下這兩個宏的使用:

BEGIN_MESSAGE_MAP(CView, CWnd)

ON_WM_PAINT()

// Standard commands for split pane

ON_COMMAND_EX(ID_WINDOW_SPLIT, &CView::OnSplitCmd)

END_MESSAGE_MAP()

省略号部分代表還有其他内容,為了簡單這裡僅寫3個。

展開後的結果如下:

const AFX_MSGMAP* CView::GetMessageMap() const

return GetThisMessageMap(); 

const AFX_MSGMAP* PASCAL CView::GetThisMessageMap()

typedef CView ThisClass;

typedef CWnd TheBaseClass;

static const AFX_MSGMAP_ENTRY _messageEntries[] =

   { 

WM_PAINT, 

0,

0, 

AfxSig_vv, 

(AFX_PMSG)(…)( &CView::OnPaint)) 

}, //ON_WM_PAINT

   … …

WM_COMMAND, 

CN_COMMAND, 

(WORD)ID_WINDOW_SPLIT, 

(WORD)ID_WINDOW_SPLIT,

AfxSigCmd_EX,

(AFX_PMSG)(…)(&CView::OnSplitCmd)) 

},// ON_COMMAND_EX

   {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } //END_MESSAGE_MAP()

static const AFX_MSGMAP messageMap =

   &TheBaseClass::GetThisMessageMap, &_messageEntries[0]

return &messageMap;

看一下函數:GetThisMessageMap

它在内部建立一個局部靜态資料結構數組_messageEntries,這說明調用過該函數該數組才會被初始化。而這兩個宏之間的部分就是用來初始化該結構數組變量的資料。

是以,從GetMessageMap()可以看出,它傳回的實際是一個局部靜态結構數組的指針。

下面詳細分析一下該函數GetThisMessageMap。

2.6.3 GetThisMessageMap

_messageEntries是AFX_MSGMAP_ENTRY結構數組,它實際就是消息映射表,記錄每一個消息ID(第一個參數)到消息處理函數指針(最後一個參數,)的映射,當然還有其它一些資訊,例如消息來自于哪個控件(該控件ID)等等,這個消息結構的其它成員以後再學習。

實際上,_messageEntries記錄的都是大部分都是目前類能夠處理的消息映射表,宏BEGIN_MESSAGE_MAP(theClass, baseClass)的第一個參數正是用在這個地方,指出能夠處理消息的函數所屬的類。

messageMap是一個結構變量,它的成員很有意思,第一個成員是一個函數指針,它指向基類(也可能是其它類)的GetThisMessageMap函數指針;上文提到的宏BEGIN_MESSAGE_MAP(theClass, baseClass)的第二個參數正是用在這個地方。

另一個成員指向_messageEntries,也就是消息映射表。

很顯然,通過該函數可以很容易找到相應的消息處理函數,即使目前類不能處理某,還可以在基類中(或指定地類中)尋找。

下面舉幾個消息映射的例子。

2.6.4 CCmdTarget的消息映射

看一下這個類消息映射表的定義:

const AFX_MSGMAP* CCmdTarget::GetMessageMap() const

return GetThisMessageMap();

const AFX_MSGMAP* CCmdTarget::GetThisMessageMap()

   { 0, 0, AfxSig_end, 0 }     // nothing here

   NULL,

   &_messageEntries[0]

return &messageMap; 

該類沒有BEGAIN…ON…END宏,看一下消息映射表_messageEntries,它隻有一個元素{ 0, 0, AfxSig_end, 0 },顯然,該元素不會處理任何消息。

再看看messageMap,第一個成員為NULL,也就是說,如果一個消息達這裡還沒有被處理,那麼這個消息再也不會被處理了。

2.6.5 CView的消息映射

先看一下CView類(這裡僅寫出部分代碼):

class AFX_NOVTABLE CView : public CWnd

...

afx_msg void OnPaint();

afx_msg BOOL OnSplitCmd(UINT nID);

DECLARE_MESSAGE_MAP()

展開這部分代碼後如下:

virtual const AFX_MSGMAP* GetMessageMap() const;

   { WM_PAINT, 0, 0, 0, AfxSig_vv, (…)( &ThisClass :: OnPaint)) },//ON_WM_PAINT

   { WM_COMMAND, CN_COMMAND, (WORD) ID_WINDOW_SPLIT, 

(WORD) ID_WINDOW_SPLIT, AfxSigCmd_EX,(...)&CView::OnSplitCmd)) },

   &CWnd::GetThisMessageMap, &_messageEntries[0]

void CView::OnPaint(){…}

BOOL CView:: OnSplitCmd(UINT nID);{…}

…表示一種強制類型轉換,這裡暫不考慮它。

看一下粗體部分字型,OnPaint和OnSplitCmd是必須定義的。

這也說明,消息WM_PAINT映射倒CView::OnPaint函數;再看一下另一個函數OnSplitCmd,它可以處理消息WM_COMMAND,但它還有兩個參數,詳情可以參看msdn,注意宏ON_COMMAND_EX展開後的代碼,這說明如果有一個WM_COMMAND消息,且它的第一個參數是CN_COMMAND,它第二個參數是ID_WINDOW_SPLIT時,才能被OnSplitCmd處理。

是以,以上說明不同的消息,它們的宏定義多少也有差別,本質上,也就是對消息映射表_messageEntries的各個元素初始化也是不同的。是以消息映射表對應的結構體AFX_MSGMAP_ENTRY各個成員正式反映了消息的不同類型,有些消息需要使用較多的成員,但有些消息使用很少的成員。

CWnd是CView的基類,它也是BEGIN_MESSAGE_MAP的第二個參數。

2.6.6 消息的分派

當視窗産生一個消息後,它從哪裡開始進入消息映射表中呢?

MFC提供了一個全局函數AfxRegisterWndClass,暫時不關心該函數是如何調用的,但可以肯定一點,當有視窗建立時,會調用該函數,看一下該函數體的部分代碼:

LPCTSTR AFXAPI AfxRegisterWndClass(…)

WNDCLASS wndcls;

wndcls.lpfnWndProc = DefWindowProc;

顯然,這個函數的作用是注冊一個視窗類WNDCLASS (如果視窗類不存在),其中,成員lpfnWndProc是一個函數指針,它指向一個消息處理函數,在視窗消息循環中(CWinApp::Run函數)中調用(可能是間接)DispatchMessage函數,該函數的作用是将消息傳給lpfnWndProc所指向的函數,這就構成了基本的視窗處理過程。

然而,MFC還不僅這麼簡單,看一下面幾個函數(部分代碼):

BOOL CWnd::CreateEx(...)

AfxHookWindowCreate(this);

void AFXAPI AfxHookWindowCreate(CWnd* pWnd)

pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,

    _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());

CWnd建立視窗時安裝了一個消息挂鈎,這個挂鈎的類型是WH_CBT,關于這種類型挂鈎詳細類型參看msd,絕大部分的windows消息(事實上我沒有遇到過有哪個消息例外)都能被該挂鈎函數過濾,而且,在windows通過正常方法處理消息之前,都會先調用挂鈎函數。

下面再看一下挂鈎函數_AfxCbtFilterHook:

_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)

if (code != HCBT_CREATEWND)

   // wait for HCBT_CREATEWND just pass others on...

   return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,

    wParam, lParam);

WNDPROC afxWndProc = AfxGetAfxWndProc();

oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,

(DWORD_PTR)afxWndProc);

WNDPROC AFXAPI AfxGetAfxWndProc()

return &AfxWndProc;

這裡,又将消息處理函數換成AfxWndProc。至此,可以清楚的知道,::DispatchMessage最終會将視窗消息傳給AfxWndProc。

2.6.7 消息的傳遞

先看看消息的起頭函數:

LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

// all other messages route through message map

CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

if (pWnd == NULL || pWnd->m_hWnd != hWnd)

   return ::DefWindowProc(hWnd, nMsg, wParam, lParam);

return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

LRESULT AFXAPI AfxCallWndProc(…)

lResult = pWnd->WindowProc(nMsg, wParam, lParam);

return lResult;

在MFC中,很多視窗類都有成員函數WindowProc(包括繼承的),大部分都是從CWnd派生來的,看一下下面幾個函數:

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

// OnWndMsg does most of the work, except for DefWindowProc call

LRESULT lResult = 0;

if (!OnWndMsg(message, wParam, lParam, &lResult))

   lResult = DefWindowProc(message, wParam, lParam);

LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)

if (m_pfnSuper != NULL)

   return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);

WNDPROC pfnWndProc;

if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)

   return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);

else

   return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);

如果OnWndMsg執行失敗,調用DefWindowProc,OnWndMsg函數做了哪些事情呢?

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)

if (message == WM_COMMAND){…(OnCommand(wParam, lParam);…}

if (message == WM_NOTIFY) {…(OnNotify(wParam, lParam);…}

if (message == WM_ACTIVATE) _AfxHandleActivate(…);

if (message == WM_SETCURSOR) _AfxHandleSetCursor(…);

從OnWndMsg可以看出,對于不同類型的消息,調用的處理函數也有不同。

for (/* pMessageMap already init'ed */; pMessageMap->pfnGetBaseMap != NULL;

    pMessageMap = (*pMessageMap->pfnGetBaseMap)())

   if (message < 0xC000)

    AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)

    … …

   else

    lpEntry = pMessageMap->lpEntries;

    while(lpEntry = AfxFindMessageEntry(lpEntry, 0xC000, 0, 0)) != NULL)

     … … 

本文轉自jetyi51CTO部落格,原文連結: http://blog.51cto.com/jetyi/294422,如需轉載請自行聯系原作者

繼續閱讀