MFC日記選----VC篇

   MFC基礎技術,聽了蘇坤老師,李全福的視頻 講座--從實例學VC,學習總結web

MFC程序的初始化過程
2.1.1 _tWinMainwindows

  • _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;app

// 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();ui

if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
   ::TranslateMessage(&(pState->m_msgCur));
   ::DispatchMessage(&(pState->m_msgCur));
}
調用了GetMessage(經過其它函數),該函數不但從消息隊列取消息,以後還將消息刪除。而後分配該消息。
2.1.10 AfxWinMain函數InitFailure後的代碼
InitFailure以後的代碼如今暫不考慮。this

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)
    {
     … … 
}
}
MFC序列化

1.序列化概念:採用數據流的方式,將數據依次寫入或者讀取文件。是二進制的存儲方式。

2.序列化相關類:CFile(文件類);CArchive(封裝了序列化操做,完成數據讀寫的具體操做);CObject中與序列化相關的兩個函數,CObject::Serialize()數據讀寫的虛函數;CObject::IsSerializable()判斷是否支持序列化。

3.序列化的使用:

       1>建立或者打開文件 CFile::Open() 

       2>定義CArchive對象

       3>使用CArchive對象進行寫(存儲」<<」)或者讀(加載」>>」);同一個文件的數據存儲和加載的順序和類型必須一致

       4>關閉CArchive對象 CArchive::Close()

       5>關閉文件 CFile::Close()

4.對象的序列化和反序列化

對象的序列化——將對象的類的信息以及類中的數據保存到文件中的過程。

對象的反序列化——根據讀取的類的信息,建立對象,並將對象的數據讀入的過程。

使用過程:

   (1).定義支持序列化的類

         1>繼承自CObject

         2>重寫CObject類的Serialize()函數

         3>在類的定義中添加宏  DECLARE_SERIAL

         4>在類的實現中添加宏 IMPLEMENT_SERIAL

   (2).建立或者打開文件 CFile::Open() 

   (3).定義CArchive對象 

   (4).將類的對象保存到CArchive對象中,或者從CArchive對象讀取

   (5).關閉CArchive對象 CArchive::Close()

   (6).關閉文件 CFile::Close()

5.對象的序列化帶來的好處

     1>   以某種存儲形式使自定義對象持久化;

     2>   將對象從一個地方傳遞到另外一個地方。

     3>   使程序更具維護性。

實例
序列化實例

[cpp]
//新建MFC的MFCSerialize工程,刪除掉自動生成的文件,並設置 屬性->連接器->系統->子系統  控制檯 (/SUBSYSTEM:CONSOLE)  
// MFCSerialize.cpp : 定義應用程序的類行爲。   
#include "stdafx.h"   
CWinApp the; 
using namespace std; 
void Store()//寫操做,存儲  
{ 
    CFile file; 
    if (!file.Open(L"c:\\serial.data",CFile::modeCreate|CFile::modeWrite)) 
    { 
        return; 
    } 
    CArchive ar(&file,CArchive::store);//定義CArchive對象  
    try 
    { 
        //執行寫操做  
        ar<<100<<12.25; 
        CString strData=L"Hello CArchive"; 
        ar<<strData; 
    }  
    catch (CException* e) 
    { 
    } 
    ar.Close(); 
    file.Close(); 
} 
void Load()//讀操做,加載  
{ 
    CFile file; 
    if (!file.Open(L"c:\\serial.data",CFile::modeRead)) 
    { 
        return; 
    } 
    CArchive ar(&file,CArchive::load); 
    int num=0; 
    double fNum=0.0f; 
    CString strData=L""; 
    ar>>num>>fNum>>strData;  
    ar.Close(); 
    file.Close(); 
    wprintf(L"nNum=%d; fNum=%.2f; %s\n",num,fNum,strData); 
} 
void main() 
{ 
    Store(); 
    Load(); 
} 

//新建MFC的MFCSerialize工程,刪除掉自動生成的文件,並設置 屬性->連接器->系統->子系統  控制檯 (/SUBSYSTEM:CONSOLE)
// MFCSerialize.cpp : 定義應用程序的類行爲。
#include "stdafx.h"
CWinApp the;
using namespace std;
void Store()//寫操做,存儲
{
CFile file;
if (!file.Open(L"c:\\serial.data",CFile::modeCreate|CFile::modeWrite))
{
  return;
}
CArchive ar(&file,CArchive::store);//定義CArchive對象
try
{
  //執行寫操做
  ar<<100<<12.25;
  CString strData=L"Hello CArchive";
  ar<<strData;
}
catch (CException* e)
{
}
ar.Close();
file.Close();
}
void Load()//讀操做,加載
{
CFile file;
if (!file.Open(L"c:\\serial.data",CFile::modeRead))
{
  return;
}
CArchive ar(&file,CArchive::load);
int num=0;
double fNum=0.0f;
CString strData=L"";
ar>>num>>fNum>>strData;
ar.Close();
file.Close();
wprintf(L"nNum=%d; fNum=%.2f; %s\n",num,fNum,strData);
}
void main()
{
Store();
Load();
}運行結果:

\

對象序列化實例

[cpp]
//新建MFC的MFCObject工程,刪除掉自動生成的文件,並設置 屬性->連接器->系統->子系統  控制檯 (/SUBSYSTEM:CONSOLE)  
// MFCObject.cpp : 定義應用程序的類行爲。   
#include "stdafx.h"    
CWinApp theApp; 
using namespace std; 
//定義支持序列化得類  
class CStudent:public CObject 
{ 
public: 
    CStudent(){} 
    CStudent(CString name,int age); 
    void Show(); 
    virtual void Serialize(CArchive& ar); 
    _DECLARE_DYNCREATE(CStudent) //動態建立宏  
    AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, CStudent*& pOb); 
private: 
    CString m_strName; 
    int m_nAge; 
}; 
CStudent::CStudent(CString name,int age) 
{ 
    m_strName=name; 
    m_nAge=age; 
} 
void CStudent::Show() 
{  
    printf("學員信息:%S,%d\n",m_strName,m_nAge);//大寫'S'在ansi中,表明後面是unic字符串    
} 
void CStudent::Serialize(CArchive& ar) 
{ 
    if (ar.IsStoring()) 
    { 
        ar<<m_strName<<m_nAge; 
    } 
    else 
    { 
        ar>>m_strName>>m_nAge; 
    } 
} 
CArchive& AFXAPI operator>>(CArchive& ar, CStudent*& pOb) 
{ 
    pOb=(CStudent*)ar.ReadObject(RUNTIME_CLASS(CStudent)); 
    return ar; 
} 
CObject* PASCAL CStudent::CreateObject() 
{ 
    return new CStudent; 
} 
_IMPLEMENT_RUNTIMECLASS(CStudent,CObject,1,CStudent::CreateObject); 
AFX_CLASSINIT _init_CStudent(RUNTIME_CLASS(CStudent)); 
void ObjStore(CStudent&stu) 
{ 
    CFile file; 
    file.Open(L"c:\\student.data",CFile::modeCreate|CFile::modeWrite); 
    CArchive ar(&file,CArchive::store); 
    ar<<&stu; 
    ar.Close(); 
    file.Close(); 
} 
void ObjLoad() 
{ 
    CFile file; 
    file.Open(L"c:\\student.data",CFile::modeRead); 
    CArchive ar(&file,CArchive::load); 
    CStudent *pStu=NULL; 
    ar>>pStu; 
    ar.Close(); 
    if (pStu) 
    { 
        pStu->Show(); 
    } 
} 
void main() 
{ 
    CStudent stu(L"ZhangShan",18);  
    ObjStore(stu);//對象的序列化  
    ObjLoad();//對象的反序列化  
} 
/*
class CStudent

...
_DECLARE_DYNCREATE(CStudent)//動態建立宏 
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar,CStudent* &pOb);

1.operator>>-建立類的對象並讀取數據
2._init_CStudent-變量展開後是一個函數AfxClassInit,將CStudent的運行時類信息保存到程序的模塊中m_classList
3.序列化的過程
    1> 獲取對象的運行時類信息:  CRuntimeClass* pClassRef    = pOb->GetRuntimeClass(); 
    2> 將類的版本和名稱寫入到Archive對象中WriteClass(pClassRef);調用 pClassRef->Store(*this);,在函數中,將類的版本、大小、名稱保存到Archive對象
    3> 調用對象的Serialize函數((CObject*)pOb)->Serialize(*this);
    4> 在Serialize函數中,將對象的數據保存到Archive對象中   ar<<m_strName<<m_nAge;


4.反序列化的過程
    1> _init_CStudent變量將類的運行時類信息保存到應用程序中。
    2> 從文件中讀取類的名稱和版本ReadClass函數,調用pClassRef = CRuntimeClass::Load(*this, &nSchema)在函數中,從Archive對象讀取類的版本、大小   和名稱。
    3> 使用從文件中讀取的類的名稱,在程序的鏈表m_classList中,去查找類的運行時信息
    AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
    for (pClass = pModuleState->m_classList; pClass != NULL;pClass = pClass->m_pNextClass)
    {
       if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
      {
          AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
          return pClass;
      }
    } 
    4> ReadClass函數執行完畢後,調用CreateObject函數     pOb = pClassRef->CreateObject();調用宏展開後的函數,建立CStudent類的對象
    5> 調用對象的Serialize函數 pOb->Serialize(*this);
    6> 在Serialize函數中,讀取對象的數據 ar>>m_strName>>m_nAge;

*/ 

//新建MFC的MFCObject工程,刪除掉自動生成的文件,並設置 屬性->連接器->系統->子系統  控制檯 (/SUBSYSTEM:CONSOLE)
// MFCObject.cpp : 定義應用程序的類行爲。
#include "stdafx.h" 
CWinApp theApp;
using namespace std;
//定義支持序列化得類
class CStudent:public CObject
{
public:
CStudent(){}
CStudent(CString name,int age);
void Show();
virtual void Serialize(CArchive& ar);
_DECLARE_DYNCREATE(CStudent) //動態建立宏
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, CStudent*& pOb);
private:
CString m_strName;
int m_nAge;
};
CStudent::CStudent(CString name,int age)
{
m_strName=name;
m_nAge=age;
}
void CStudent::Show()
{
printf("學員信息:%S,%d\n",m_strName,m_nAge);//大寫'S'在ansi中,表明後面是unic字符串 
}
void CStudent::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
  ar<<m_strName<<m_nAge;
}
else
{
  ar>>m_strName>>m_nAge;
}
}
CArchive& AFXAPI operator>>(CArchive& ar, CStudent*& pOb)
{
pOb=(CStudent*)ar.ReadObject(RUNTIME_CLASS(CStudent));
return ar;
}
CObject* PASCAL CStudent::CreateObject()
{
return new CStudent;
}
_IMPLEMENT_RUNTIMECLASS(CStudent,CObject,1,CStudent::CreateObject);
AFX_CLASSINIT _init_CStudent(RUNTIME_CLASS(CStudent));
void ObjStore(CStudent&stu)
{
CFile file;
file.Open(L"c:\\student.data",CFile::modeCreate|CFile::modeWrite);
CArchive ar(&file,CArchive::store);
ar<<&stu;
ar.Close();
file.Close();
}
void ObjLoad()
{
CFile file;
file.Open(L"c:\\student.data",CFile::modeRead);
CArchive ar(&file,CArchive::load);
CStudent *pStu=NULL;
ar>>pStu;
ar.Close();
if (pStu)
{
  pStu->Show();
}
}
void main()
{
CStudent stu(L"ZhangShan",18);
  ObjStore(stu);//對象的序列化
  ObjLoad();//對象的反序列化
}
/*
class CStudent

...
_DECLARE_DYNCREATE(CStudent)//動態建立宏
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar,CStudent* &pOb);

1.operator>>-建立類的對象並讀取數據
2._init_CStudent-變量展開後是一個函數AfxClassInit,將CStudent的運行時類信息保存到程序的模塊中m_classList
3.序列化的過程
1> 獲取對象的運行時類信息:  CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
    2> 將類的版本和名稱寫入到Archive對象中WriteClass(pClassRef);調用 pClassRef->Store(*this);,在函數中,將類的版本、大小、名稱保存到Archive對象
3> 調用對象的Serialize函數((CObject*)pOb)->Serialize(*this);
    4> 在Serialize函數中,將對象的數據保存到Archive對象中   ar<<m_strName<<m_nAge;


4.反序列化的過程
1> _init_CStudent變量將類的運行時類信息保存到應用程序中。
2> 從文件中讀取類的名稱和版本ReadClass函數,調用pClassRef = CRuntimeClass::Load(*this, &nSchema)在函數中,從Archive對象讀取類的版本、大小 和名稱。
3> 使用從文件中讀取的類的名稱,在程序的鏈表m_classList中,去查找類的運行時信息
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
for (pClass = pModuleState->m_classList; pClass != NULL;pClass = pClass->m_pNextClass)
{
    if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
   {
       AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
       return pClass;
   }
}
4> ReadClass函數執行完畢後,調用CreateObject函數  pOb = pClassRef->CreateObject();調用宏展開後的函數,建立CStudent類的對象
5> 調用對象的Serialize函數 pOb->Serialize(*this);
6> 在Serialize函數中,讀取對象的數據 ar>>m_strName>>m_nAge;

*/運行結果:
圖略

MFC學習一籮筐

相關文章
相關標籤/搜索