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,如需轉載請自行聯系原作者