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类的静态变量。
调用:
CRuntimeClass* pClassThis = GetRuntimeClass();
GetRuntimeClass从CObject继承下来的,它返回CView::class CView。
有意思的调用是pClassThis->IsDerivedFrom(pClass);
看看IsDerivedFrom的简化代码:
BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const
{
// simple SI case
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{
public:
static const CRuntimeClass classCView;//静态变量
virtual CRuntimeClass* GetRuntimeClass() const;//虚函数声明
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,
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()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{ 0, 0, AfxSig_end, 0 } // nothing here
};
static const AFX_MSGMAP messageMap =
{
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()
};
BEGIN_MESSAGE_MAP(CView, CWnd)
ON_WM_PAINT()
ON_COMMAND_EX(ID_WINDOW_SPLIT, &CView::OnSplitCmd)
END_MESSAGE_MAP()
展开这部分代码后如下:
class AFX_NOVTABLE CView : public CWnd
{
...
afx_msg void OnPaint();
afx_msg BOOL OnSplitCmd(UINT nID);
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;
};
const AFX_MSGMAP* CCmdTarget::GetMessageMap() const
{
return GetThisMessageMap();
}
const AFX_MSGMAP* CCmdTarget::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{ 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)) },
{ 0, 0, AfxSig_end, 0 } // nothing here
};
static const AFX_MSGMAP messageMap =
{
&CWnd::GetThisMessageMap, &_messageEntries[0]
};
return &messageMap;
}
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);
return lResult;
}
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,如需转载请自行联系原作者