MFC核心技术探索

本文采用逐步调试的方法,带你一起探索MFC程序的执行流程、窗体创建、消息映射、运行时类型识别、对象动态创建这些核心机制。

相信读者在深入理解这些核心机制后,会由衷感叹于MFC框架实现的精妙,在使用MFC框架进行开发时也能“胸中自有丘壑”!

MFC程序的执行流程

以下是最小的MFC程序,“麻雀虽小,五脏俱全”,借此程序研究MFC程序的基本执行流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <afxwin.h>
// 创建自定义框架类
class CMyFrameWnd :public CFrameWnd {};
// 创建自定义程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
};
// 程序爆破点
CMyWinApp theApp;

从本质上讲,MFC框架就是采用面向对象的方式对Win32库进行封装。进一步的抽象,使开发者不需要关注过多的技术细节,可以极大提升软件开发效率;但要想熟练掌握MFC并达到游刃有余的境界,必须向底层求索,将Win32和MFC联合对照打通理解脉络。

以上程序的主要执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 先进行全局变量初始化
CMyWinApp{
CWinApp(){
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
// 初始化完成之后,就可以在进程的任何地方使用AfxGetThread()、AfxGetApp()两个全局函数获取theApp的地址
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
}
}
// 主函数
WinMain(){
AfxWinMain(){
// 先获取theApp指针
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// 为theApp的一些变量进行赋值
AfxWinInit();
// 初始化程序
pApp->InitApplication();
// 初始化实例,在这里创建窗体
pThread->InitInstance(){
pFrame->Create();
pFrame->ShowWindow();
pFrame->UpdateWindow();
}
// 消息循环
pThread->Run(){
CWinThread::Run(){
for(;;){
while(没有消息)
OnIdle();
do{
if(GetMessage()抓到WM_QUIT消息)
return ExitInstance();
}while(抓消息)
}
}
}
}
}

其中几个关键函数都可以由开发者重写:InitApplication()、InitInstance()、OnIdle()、Run()。

MFC程序的窗体创建

以上最小MFC程序创建窗体的代码是:

1
2
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase");

其具体流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase"){
// 加载菜单
LoadMenu(hInst, lpszMenuName);
CreateEx(...){
CreateEx(...){
// 构造窗体类实例
CREATESTRUCT cs;
cs.lpszClass=NULL; // 下面会赋值
...
PreCreateWindow(cs){
AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG){
WNDCLASS wndcls;
wndcls.lpfnWndProc = DefWindowProc; // 后面修改!!!
...
_AfxRegisterWithIcon(&wndcls,_afxWndFrameOrView...){
pWndCls->lpszClassName = lpszClassName;
LoadIconW(...);
RegisterClass(&wndCls);
}
}
cs.lpszClass = _afxWndFrameOrView; //“AfxFrameOrView140sd”
}
AfxHookWindowCreate(pFrame){
// 当前程序线程信息
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
// 埋下一个类型位WH_CRT钩子
::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
// 将pFrame保存到全局变量中
pThreadState->m_pWndInit = pFrame;
}
// 调用Windows API函数创建窗体
CreateWindowEx(...); // 此函数一产生就会到对应的钩子处理函数中
}
}
}
// WH_CRT钩子的处理函数
_AfxCbtFilterHook(){
// 获取第三个全局变量
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
// 获取pFrame
CWnd* pWndInit = pThreadState->m_pWndInit;
// 拿到窗口句柄
HWND hWnd = (HWND)wParam;
// 将pFrame和窗口句柄进行绑定
pWndInit->Attach(hWnd){
CHandleMap* pMap = afxMapHWND(TRUE){
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
pState->m_pmapHWND = new CHandleMap();
}
// 建立从窗口句柄到pFrame的映射
pMap->SetPermanent(m_hWnd = hWndNew/*将句柄保存到pFrame的成员变量中*/,pFrame)
m_permanentMap[(LPVOID)h] = pFrame;
}
}
// 更改窗口函数为afxWndProc
(WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
}

MFC窗体创建异常迂回,但是包含了注册窗体类、创建窗体、设置窗体处理函数等核心步骤,与Win32桌面程序一致。

为了能让开发者自定义各类消息处理函数,在消息处理函数部分会有大文章,下一章将重点跟踪该步骤。

MFC程序的消息映射

消息处理的流程

在不使用MFC消息映射机制的前提下,要自定义消息处理函数,可以采取以下方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <afxwin.h>
// 创建自定义框架类
class CMyFrameWnd :public CFrameWnd {
public:
// 重写CMyFrameWnd类的WindowProc()虚函数
virtual LRESULT WindowProc(UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_CREATE:
AfxMessageBox("WM_CREATE消息被处理!");
break;
}
return CFrameWnd::WindowProc(msgID, wParam, lParam);
}
};
// 创建自定义程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
};
// 程序爆破点
CMyWinApp theApp;

上节中,窗体处理函数最终被设置为afxWndProc,可以推断:afxWndProc窗口处理函数肯定会调用WindowProc函数。

那么MFC框架是怎么从最初的消息处理函数一步步进入到我们自定义的消息处理函数里面的,下面一探究竟:

1
2
3
4
5
6
7
8
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam){
// 从句柄得到pFrame
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam){
// 进到虚函数中
pWnd->WindowProc(nMsg, wParam, lParam)
}
}

消息映射机制

在不重写WinfowProc虚函数的前提下处理消息,就需要用到消息映射机制,基本用法如下:

1
2
3
4
5
类内声明宏:DECLARE_MESSAGE_MAP()
类外实现宏:
BEGIN__MESSAGE_MAP(theClass,baseClass)
ON_MESSAGE(WM_CREATE,OnCreate)
END_MESSAGE_MAP()

基本程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <afxwin.h>
// 创建自定义框架类
class CMyFrameWnd :public CFrameWnd {
DECLARE_MESSAGE_MAP()
public:
LRESULT OnCreate(WPARAM wParam,LPARAM lParam) {
AfxMessageBox("WM_CREATE消息被处理!");
return 0;
}
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_MESSAGE(WM_CREATE,OnCreate)
END_MESSAGE_MAP()

// 创建自定义程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
};
// 程序爆破点
CMyWinApp theApp;

下面展开宏,还原真相:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CMyFrameWnd :public CFrameWnd {
//DECLARE_MESSAGE_MAP()
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
virtual const AFX_MSGMAP* GetMessageMap() const;

public:
LRESULT OnCreate(WPARAM wParam, LPARAM lParam) {
AfxMessageBox("WM_CREATE消息被处理!");
return 0;
}
};

//BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
// ON_MESSAGE(WM_CREATE, OnCreate)
//END_MESSAGE_MAP()
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const{
return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap(){
static const AFX_MSGMAP_ENTRY _messageEntries[] = {
{ WM_CREATE, ..., &OnCreate },
{0, ..., 0 }
};
static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
return &messageMap;
}

_messageEntries和messageMap在父类和子类之间形成了一个链表,如下图所示:

继续上一节消息处理流程的分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 进到虚函数中,以下为WM_CREATE消息,WM_COMMAND、WM_NOTIFY等路径不一样
pWnd->WindowProc(nMsg, wParam, lParam){
OnWndMsg(message, wParam, lParam, &lResult){
// 获取本类的链表节点
pMessageMap = GetMessageMap();
const AFX_MSGMAP_ENTRY* lpEntry;
// 遍历链表,从子类向父类遍历
for (; pMessageMap->pfnGetBaseMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)()){
// 找到了就返回数组元素的地址
lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0);
if(lpEntry != NULL){
goto LDispatch;
}
}
LDispatch:
// 调用lpEntry->pfn,处理消息
}
}

个性化消息映射

MFC中的消息分为三类:

  • 标准Windows消息:可使用ON_WM_XXX宏处理
  • 自定义消息:可使用ON_MESSAGE宏处理
  • 命令消息:可使用ON_COMMAND宏处理,可以在任何类中处理,但是有优先顺序

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <afxwin.h>
class CMyFrameWnd :public CFrameWnd {
DECLARE_MESSAGE_MAP()
public:
afx_msg int OnCreate(LPCREATESTRUCT pcs) {
AfxMessageBox("WM_CREATE消息被处理!");
return CFrameWnd::OnCreate(pcs);
}
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
// ON_WM_CREATE()
{ WM_CREATE, 0, 0, 0, AfxSig_is,
(AFX_PMSG)(AFX_PMSGW)(static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },
END_MESSAGE_MAP()

class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
};
CMyWinApp theApp;

ON_COMMAND消息来源于菜单或工具栏按钮的点击事件,下面简要介绍菜单(主菜单、上下文菜单)和工具栏的加载。

菜单加载:

  • 方式1:pFrame->Create(NULL, “MFCBase”)中可以传递菜单资源ID
  • 方式2:在WM_CREATE消息处理函数中加载菜单
  • 设置菜单勾选状态:在WM_INITMENUPOPUP消息处理函数中调用::CheckMenuItem()函数

上下文菜单:

  • 实现WM_CONTEXTMENU消息处理函数,调用::TrackPopupMenu()函数

工具栏加载:CToolBarCtrl、CToolBar

  • 步骤1:添加工具栏资源:可视化制作
  • 步骤2:创建工具栏(CToolBar::CreateEx)
  • 步骤3:加载工具栏(CToolBar::LoadToolBar)
  • 步骤4:设置工具栏的停靠(CToolBar::EnableDocking、CFrameWnd::EnableDocking、CFrameWnd::DockControlBar)

MFC程序的运行时类型识别

在运行时能获得对象是否属于某个类的信息就是“运行时类信息机制”,基本用法如下:

1
2
3
4
类必须派生自CObject
类内声明宏:DECLARE_DYNAMIC(theClass)
类外实现宏:IMPLEMENT_DYNAMIC(theClass,baseClass)
使用CObject::IsKindOf()就可以判断对象是否属于某个类

测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
DECLARE_DYNAMIC(CAnimal)
};
IMPLEMENT_DYNAMIC(CAnimal, CObject)

class CDog :public CAnimal {
DECLARE_DYNAMIC(CDog)
};
IMPLEMENT_DYNAMIC(CDog, CAnimal)

int main() {
CAnimal a;
CDog d;

if (d.IsKindOf(RUNTIME_CLASS(CDog)))
cout << "YES" << endl;
else
cout << "NO" << endl;

system("pause");
return 0;
}

下面展开宏,还原真相:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
//DECLARE_DYNAMIC(CAnimal)
public:
static const CRuntimeClass classCAnimal;
virtual CRuntimeClass* GetRuntimeClass() const;
};

//IMPLEMENT_DYNAMIC(CAnimal, CObject)
AFX_COMDAT const CRuntimeClass CAnimal::classCAnimal = {
"CAnimal",
sizeof(class CAnimal),
0xFFFF,
NULL,
RUNTIME_CLASS(CObject),
NULL,
NULL
};

CRuntimeClass* CAnimal::GetRuntimeClass() const{
//return RUNTIME_CLASS(CAnimal);
return (CRuntimeClass*)(&CDog::classCDog);
}


class CDog :public CAnimal {
DECLARE_DYNAMIC(CDog)
};
IMPLEMENT_DYNAMIC(CDog, CAnimal)

int main() {
CAnimal a;
CDog d;

// (CRuntimeClass*)(&CDog::classCDog)
if (d.IsKindOf(RUNTIME_CLASS(CDog)))
cout << "YES" << endl;
else
cout << "NO" << endl;

system("pause");
return 0;
}

每个类中都包含一个CRuntimeClass类变量,其中包含父类信息,构成了一个链表,如下图所示:

IsKindOf(CRuntimeClass*)的执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IsKindOf(const CRuntimeClass* pClass){
// 获取当前对象的运行是类信息
CRuntimeClass* pClassThis = GetRuntimeClass();
// 判断该对象是不是参数类的派生类
pClassThis->IsDerivedFrom(pClass){
// 从派生类向父类方向遍历链表
const CRuntimeClass* pClassThis = this;
while (pClassThis != NULL){
if (pClassThis == pClass)
return TRUE;
pClassThis = pClassThis->m_pBaseClass;
}
return FALSE;
}
}

MFC程序的对象动态创建

在不知道类名的情况下创建类对象的机制就是“动态创建机制”,基本用法如下:

1
2
3
4
类必须派生自CObject
类内声明宏:DECLARE_DYNCREATE(theClass)
类外实现宏:IMPLEMENT_DYNCREATE(theClass,baseClass)
使用CRuntimeClass::CreateObject()就可以创建类对象

测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
DECLARE_DYNAMIC(CAnimal)
};
IMPLEMENT_DYNAMIC(CAnimal, CObject)

// 动态创建CDog
class CDog :public CAnimal {
DECLARE_DYNCREATE(CDog)
};
IMPLEMENT_DYNCREATE(CDog, CAnimal)

int main() {

system("pause");
return 0;
}

下面展开宏,还原真相:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
DECLARE_DYNAMIC(CAnimal)
};
IMPLEMENT_DYNAMIC(CAnimal, CObject)

class CDog :public CAnimal {
// DECLARE_DYNCREATE(CDog)
public:
static const CRuntimeClass classCDog;
virtual CRuntimeClass* GetRuntimeClass() const;
static CObject* PASCAL CreateObject();
};

//IMPLEMENT_DYNCREATE(CDog, CAnimal)
CObject* PASCAL CDog::CreateObject() {
return new CDog;
}
AFX_COMDAT const CRuntimeClass CDog::classCDog = {
"CDog",
sizeof(class CDog),
0xFFFF,
CDog::CreateObject,
RUNTIME_CLASS(CAnimal),
NULL,
NULL
};
CRuntimeClass* CDog::GetRuntimeClass() const{
return (CRuntimeClass*)(&CDog::classCDog);
}

int main() {
CDog* pDog = (CDog*)RUNTIME_CLASS(CDog)->CreateObject();
return 0;
}

RUNTIME_CLASS(CDog)->CreateObject()的执行流程如下:

1
2
3
4
RUNTIME_CLASS(CDog)->CreateObject(){
// m_pfnCreateObject = CDog::CreateObject
pObject = (*m_pfnCreateObject)();
}

评论