初学者分析MFC代码[转]
常有人说vc\c++入门难(请注意:vc是IDE,c++是 language),当然,没有任何编程基础一开始就vc\mfc的搞,肯定难啦。这里主要针对的是有一点c语言基础,编过一些结构化程序的入门者(典型的是相当一部分中国大学生,他们在学校里用tc编过一些小程序,但没有或者甚少接触过面向对象编程,我就是这样的)。这篇文章针对这部分初学者一开始最不明白的地方一一解惑,没有什么原理性的东西,有的只是表面上看到的,从我们看到的来解释一下为什么是这样(我会在后续文章中对所删掉的代码进行解释)。当中涉及一些英文的翻译,我英语水平差(也是大多数人的通病,或者即使他们英语水平比较好,但他们也很容易忽略掉相当多的东西),翻译的肯定不准确,但相信大部分人都能看得明白。同时他们也是非常心急的,一开始就搞mfc。好,我们就从mfc开始,先给它减肥。
打开vc6,File->New,选择Projects页(默认),选择MFC AppWizard(exe),在Project name 输入Test1,按OK继续。
好了,What type of application would you like to create?问我们要创建哪一种类型的应用程序呢?也许你已经注意到了,前面选择的MFC AppWizard 中的App就是指application。选择Dialog based,然后直接按Finish结束,至于Next下面有什么东西,以后再慢慢探索。
编译,运行,一个对话框程序就出来了,很多教材在这个地方都会故作惊讶,哇,一行代码都没写就编了一个程序呀!欺负咱们不懂!
咱们初学者开始探索一下这个程序。
首先,到工程目录下看看有什么东西。目前不需要了解每个文件是干什么的,大概知道有些什么东西就可以了。我们这里只关注几个文件:所有.h和.cpp文件以及一个dsw文件,以后我们双击dsw文件就可以打开这个工程,你可以看到它的文件类型是Project Workspace,我称之为工程工作空间。另外,我们还关注一个Debug文件夹,打开发现里面有一个Test1.exe,双击看看,它就是我们编译好的程序。
好了,回到vc来(这里不准备介绍太多的vc工作环境)。
编程当然要看代码(主要是StdAfx、Test1、Test1Dlg的.h和.cpp文件,其它暂时忽略),第一个碰到的问题就是:代码太多了,不知道是干什么用的,找不到程序开始的地方(main函数),而且有很多注释。好,既然是这样,不是有很多注释吗?既然是注释,是否就可以删掉?马上试一试(删之前先备份,以后还要用到)。删掉所有注释后的代码如下:(由于这样确实罗嗦,就不贴出来了,总之是删掉所有//开头的注释语句)
好了,一个减肥后的mfc代码出来了,你可以想像的出来,我们只是删掉了注释,程序应该可以继续执行。编译,运行,没问题。这个减肥后的mfc代码,仍然是太长了,仍然有很多难以看懂的代码,下一篇,我们让它继续瘦身!
上一篇我们把所有注释都去掉了,再来看看还能不能删掉一些代码?(代码篇幅较长,请耐心看)
先看StdAfx.h:
#if !defined(AFX_STDAFX_H__582A90CE_7355_479E_9E7F_F376479662DA__INCLUDED_)
#define AFX_STDAFX_H__582A90CE_7355_479E_9E7F_F376479662DA__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif
#define VC_EXTRALEAN
#include <afxwin.h>
#include <afxext.h>
#include <afxdisp.h>
#include <afxdtctl.h>
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>
#endif
#endif
include我们是熟悉的,其它的代码......就不太懂了,能删掉吗?试一试,把#include <afxwin.h>前面的都删掉,编译,出问题了fatal error C1020: unexpected #endif,哦,多了一个#endif,原来刚才删的时候前面有一个#if是和#endif搭配的,把最后一个#endif删掉,编译,运行,没问题。既然是这样,把#include <afxdtctl.h>后面的也删掉,这样,最后就只剩下:
#include <afxwin.h>
#include <afxext.h>
#include <afxdisp.h>
#include <afxdtctl.h>
再看StdAfx.cpp,天啊,居然只有一句:#include "stdafx.h",我们知道,cpp是实现文件,那不等于什么都没有做吗?我们试着把StdAfx.cpp整个从工程里面删掉(在FileView里面删掉,然后关闭工程,在资源管理器里面把这个文件删掉,重新打开工程)。编译,又出问题了fatal error C1083: Cannot open precompiled header file: 'Debug/Test1.pch': No such file or directory,看来这个还不能随便删:)。把这个工程关闭,打开我们备份(注意:我们是在搞破坏,每一步都要做好备份)的工程。
继续看其它文件,和上面类似,最后的Test1.h是这样的:
#include "resource.h"
class CTest1App : public CWinApp
{
public:
CTest1App();
public:
virtual BOOL InitInstance();
DECLARE_MESSAGE_MAP()
};
最后的Test1.cpp是这样的:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
BEGIN_MESSAGE_MAP(CTest1App, CWinApp)
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
CTest1App::CTest1App()
{
}
CTest1App theApp;
BOOL CTest1App::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#endif
CTest1Dlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
}
else if (nResponse == IDCANCEL)
{
}
return FALSE;
}
最后的Test1Dlg.h是这样的:
class CTest1Dlg : public CDialog
{
public:
CTest1Dlg(CWnd* pParent = NULL);
enum { IDD = IDD_TEST1_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
HICON m_hIcon;
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
};
最后的Test1Dlg.cpp是这样的:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CTest1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()
BOOL CTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
return TRUE;
}
void CTest1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
void CTest1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
HCURSOR CTest1Dlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
你一定要有耐心仔细观察代码!!
还能再删掉一些吗?能,仔细看看Test1.cpp,在BOOL CTest1App::InitInstance()中有:
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#endif
上面这些代码是干什么的?跟前面的有点像,应该可以删掉吧?别急!新建一个跟我们这个几乎一样的基于对话框的工程Temp,在选择了Dialog based后别按Finish,Next,你会发现有3项被选中了,把3D controls、Active Controls去掉后Finish,打开Temp.cpp,你会发现那些代码没有了,还犹豫什么?在Test1.cpp中也把它删掉吧!
再仔细一点,你会发现:
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
}
else if (nResponse == IDCANCEL)
{
}
实际起作用的也就是dlg.DoModal(),其它的删掉。
删了这么多,编译试一试吧,没问题,运行,没问题。按F1试一试?出来一个windows 帮助的对话框,提示我们没有Test1.hlp文件,要不要查找?当然不!那是我们这个程序的帮助文件,我们没有做这个文件,它是不存在的。回到代码里来,找到
BEGIN_MESSAGE_MAP(CTest1App, CWinApp)
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
这是什么暂时不用管,我只看到里面有什么Help字样,估计跟我们刚才的操作有关,删掉,编译,又出问题了error LNK2001: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall CTest1App::GetMessageMap(void)const " (?GetMessageMapCTest1AppMBEPBUAFX_MSGMAPXZ),出现unresolved external symbol????不知该如何翻译好,不懂的自己查字典吧。我们到Test1.h里看看,有一句DECLARE_MESSAGE_MAP(),跟刚才删掉的都有MESSAGE_MAP,把它也删掉。编译运行没问题,按F1没反应,一切猜测证明是正确的:)。
在程序标题栏单击右键,点关于,出来一个“关于对话框”,这个大家都很熟悉。它对我们整个程序没什么影响,把它删掉吧。
它在Test1Dlg.cpp里,找到了带有AboutDlg字样的代码没有?删吧!(再次提醒,注意备份)一大段:
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()
在BOOL CTest1Dlg::OnInitDialog()中的:
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
还要把:
void CTest1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
改为:
void CTest1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
CDialog::OnSysCommand(nID, lParam);
}
然后再到ResourceView里面把about对话框的面板也删掉,编译运行,你会发现“关于”对话框没有了。
加油,再接再厉!仔细看看,有个:
void CTest1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
看字面意思是做数据交换,目前我们没有做任何数据交换啊,删掉试一试,同时把Test1Dlg.h中关于这个函数的声明virtual void DoDataExchange(CDataExchange* pDX);也删掉。编译运行都没问题。
代码越来越少了:)。再看:
BOOL CTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
return TRUE;
}
SetIcon应该是设置图标的,Icon就是图标的意思嘛,删!再运行,发现标题栏的那个图标没有了,不影响我们的核心。于是把所有跟Icon有关的代码删掉,代码较多,列出删掉代码后的Test1Dlg.h和Test1Dlg.cpp:
////////////////////////////////////////////////////////////////
Test1Dlg.h:
lass CTest1Dlg : public CDialog
{
public:
CTest1Dlg(CWnd* pParent = NULL);
enum { IDD = IDD_TEST1_DIALOG };
protected:
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////
Test1Dlg.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{
}
BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
END_MESSAGE_MAP()
BOOL CTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
return TRUE;
}
void CTest1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
CDialog::OnSysCommand(nID, lParam);
}
void CTest1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
}
else
{
CDialog::OnPaint();
}
}
然后在ResourceView里面,点开Icon文件夹,把那个图标删掉,rebuild all。你会发现标题栏图标没有了,在资源管理器里面打开Debug文件夹,那个exe文件的图标也变成了那种Dos程序的图标了。
今天就到这里,总结一下:
///////////////////////////////////////////////////
StdAfx.h:
#include <afxwin.h>
#include <afxext.h>
#include <afxdisp.h>
#include <afxdtctl.h>
//////////////////////////////////////////////////
StdAfx.cpp:
#include "stdafx.h"
/////////////////////////////////////////////////
Test1.h:
#include "resource.h"
class CTest1App : public CWinApp
{
public:
CTest1App();
public:
virtual BOOL InitInstance();
};
//////////////////////////////////////////////////
Test1.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1App::CTest1App()
{
}
CTest1App theApp;
BOOL CTest1App::InitInstance()
{
CTest1Dlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE;
}
///////////////////////////////////////////////////
Test1Dlg.h:
lass CTest1Dlg : public CDialog
{
public:
CTest1Dlg(CWnd* pParent = NULL);
enum { IDD = IDD_TEST1_DIALOG };
protected:
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////
Test1Dlg.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{
}
BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
END_MESSAGE_MAP()
BOOL CTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
return TRUE;
}
void CTest1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
CDialog::OnSysCommand(nID, lParam);
}
void CTest1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
}
else
{
CDialog::OnPaint();
}
}
代码还多吗?下一篇继续!
从前两篇我们得出一个经验:要删代码,先从cpp文件找起(废话,这是实现文件啊)。
闲话休提,看看Test1.cpp:(为了看起来方便一些,我不得不一次又一次的贴代码,还好,代码会越来越少)
/////////////////////////////////////////////////////
Test1.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1App::CTest1App()
{
}
CTest1App theApp;
BOOL CTest1App::InitInstance()
{
CTest1Dlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE;
}
///////////////////////////////////////////////////
CTest1App::CTest1App()
{
}
这是什么?CTest1App出现了2次,函数名和类名一样?对,这就是构造函数!可是它里面是空的,什么都没有构造,不说了,删!当然,.h中的声明也要一起删啊。
还有,m_pMainWnd = &dlg这一句,仅仅是一个赋值操作,并且我们发现m_pMainWnd在整个代码中只出现了一次(可利用搜索功能),似乎没有用到,于是把它也删了。编译,运行,还是没有问题。
细心的你还发现CTest1App theApp这一句仅仅是声明了一个变量,并且theApp也只出现了一次,毫不犹豫,删,编译,没问题,运行,咣当,出现了一个对话框,什么什么内存不能读。这种问题对初学者是致命的,大不了不删呗,加回去!让我们记住,theApp是一个很重要的东西。
再仔细观察Test1Dlg.cpp。
///////////////////////////////////////////////////////////
Test1Dlg.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{
}
BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
END_MESSAGE_MAP()
BOOL CTest1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
return TRUE;
}
void CTest1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
CDialog::OnSysCommand(nID, lParam);
}
void CTest1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
}
else
{
CDialog::OnPaint();
}
}
///////////////////////////////////////////////////////////////
先看OnSysCommand这个函数,从函数名来看,Sys是system的意思,这是系统命令函数?要小心!回忆一下我们在这个应用程序上的操作,上面有两个按钮,“确定”、“取消”,都可以退出程序;在标题栏上有个单击右键的操作,出来一个菜单,现在只剩下“移动”和“关闭”两个操作了,对了,对话框右上角还有个“×”可以关闭。所有的操作都在这里了,会不会和OnSysCommand有关?删掉试一试,同时也删掉它在.h中的声明afx_msg void OnSysCommand(UINT nID, LPARAM lParam)以及在BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)里面的一句ON_WM_SYSCOMMAND(),这是什么就先不要管了;。又要编译运行了,没有任何变化,是不是意味着我们这些操作和OnSysCommand无关?实际上是有关的,目前不必理会。
跟着我们再看看OnInitDialog(),从函数名来看,是做对话框初始化工作的,但我们好像没有做什么初始化啊,删!不用编译运行了,我保证,程序还可以运行:)。
还有一个OnPaint函数,看函数名应该是画东西的(奇怪,画什么东西啊?)。先告诉你IsIconic()是判断是否有最小化的,我们的对话框不能最小化,所以这段代码可以删,剩下:
void CTest1Dlg::OnPaint()
{
CDialog::OnPaint();
}
运行没问题,干脆把它一整个删掉吧,同时.h中的声明也可以删掉,那么
BEGIN_MESSAGE_MAP(CTest1Dlg, CDialog)
ON_WM_PAINT()
END_MESSAGE_MAP()
中的ON_WM_PAINT()也可以删掉(都有paint嘛),就剩下BEGIN_MESSAGE_MAP然后就马上END了,应该也可以删掉,它在.h中的声明DECLARE_MESSAGE_MAP()也可以删掉,啊,删了好多,运行,没问题!
还能再删吗?不能了吧?
看看我们的成果(代码少得不能再少了,大部分是include头文件):
////////////////////////////////////////////////////////
StdAfx.h:
#include <afxwin.h>
#include <afxext.h>
#include <afxdisp.h>
#include <afxdtctl.h>
///////////////////////////////////////////////////////////
StdAfx.cpp:
#include "stdafx.h"
////////////////////////////////////////////////////////
Test1.h:
#include "resource.h"
class CTest1App : public CWinApp
{
public:
virtual BOOL InitInstance();
};
///////////////////////////////////////////////////////
Test1.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1App theApp;
BOOL CTest1App::InitInstance()
{
CTest1Dlg dlg;
dlg.DoModal();
return FALSE;
}
//////////////////////////////////////////////////
Test1Dlg.h:
class CTest1Dlg : public CDialog
{
public:
CTest1Dlg(CWnd* pParent = NULL);
enum { IDD = IDD_TEST1_DIALOG };
};
/////////////////////////////////////////////////////
Test1Dlg.cpp:
#include "stdafx.h"
#include "Test1.h"
#include "Test1Dlg.h"
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{
}
在MFC瘦身代码中,你或者已经发现有两个特殊的文件,StdAfx.hS和tdAfx.cpp,它们仅仅是include头文件,什么也没做。如果你够细心,你还会发现在每个cpp文件最上面都include了StdAfx.h,这到底是干什么的呢?可以去掉吗?我们在Test1.cpp把#include "stdafx.h"这一句去掉,编译发现出现这样的错误:fatal error C1010: unexpected end of file while looking for precompiled header directive。意思大概是找不到预编译指示。这个文件到底是干什么用的?其实wizard已经告诉我们答案了,答案就在“注释” 里面!什么?注释?不是已经删掉了吗?是的,就是在我们删掉的注释里面,所以,wizard所做的每一件事都是有道理的。那我们为什么要删掉注释?不就是为了能让你集中注意力看关键的东西嘛!还记得我们一开始备份的工程吗?打开看看:
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
哦,原来它是用来包含一些标准的系统头文件以及在工程中一些经常用到的但又很少改动的头文件(到此看到我说的话,你应该体会到英语的重要性了)。再看StdAfx.cpp的注释:
// stdafx.cpp : source file that includes just the standard includes
// Test1.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
看第一行,的确如此,在stdafx.cpp只有一行#include "StdAfx.h"。Test1.pch是预编译头(可以在工程目录下的debug文件夹找到),到底有什么作用呢?答案是这样的:VC++程序一般包含的头文件都比较复杂,如果每次编译都分析每一行代码(其中很多代码是一直不变的),将耗费大量时间在重复分析上面,因此,有必要把一些不变的结果暂时保存起来,留待下次使用,以节省编译时间。怎么使用Test1.pch呢?我们要指定一个头文件StdAfx.h,当然,也可以是别的名字,在Project->settings里的c/c++页,Category选择Precompiled Headers,选中Use precompiled header file,在Through header里就可以修改名字了。我猜是因为.h文件不能编译,因此还需要一个cpp(stdafx.cpp)文件来生成pch文件。
既然这个pch预编译头是用来节省时间的,是不是说我们也可以不用?当然,我们有的是时间。首先在Project->settings里的c/c++页,Category选择Precompiled Headers,选中Not using precompiled headers,然后在Test1.cpp和Test1Dlg.cpp中把#include "stdafx.h"去掉,加上StdAfx.h中的内容,跟着在工程里面删掉StdAfx.h和StdAfx.cpp,编译运行都没有问题!当然,这不是一个聪明的做法!不要胡闹了,把关于预编译的修改复原。
还有一个小问题:为什么StdAfx.h要在cpp中include而不在.h中?那是为了避免头文件的重复包含。
下面我们来看看这个程序到底是怎么执行的。对于初学者来说,我有一个比较好的方法,姑且称之为“断点大法”。我们把所有代码都加上断点,然后F5进行调试。当然,这样做你会发现vc会提示你有些断点是无效的。好了,GO,一直到程序运行,出来窗口,然后关闭。我把程序执行步骤列出来:
////////////////////////////////////////////////////////
Test1.cpp:
#include .....
CTest1App theApp;/*第1步*/
BOOL CTest1App::InitInstance()
{/*第2步*/
CTest1Dlg dlg;/*第3步*/
dlg.DoModal();/*第6步*/
return FALSE;/*第7步*/
}/*第8步*/
////////////////////////////////////////////////////////
Test1Dlg.cpp:
#include .....
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{/*第4步*/
}/*第5步*/
其中,第6步到第7步之间需要你在对话框中点“确定”或“取消”按钮或者点标题栏中的“×”退出程序。
第1步:
先看看CTest1App theApp的注释:// The one and only CTest1App object。又看注释?呵呵,你可以打开另一个vc,然后打开一开始的工程查看。theApp是应用程序类的一个对象,代表了一个应用程序,这是一个单独的全局的对象。提示:你也可以改为其它的名字。
第2步:
进入应用程序类的一个InitInstance()函数(注释:// CTest1App initialization,从函数名也能看出来)。
第3步:
跟着声明一个CTest1Dlg对象dlg。
第4、5步:
CTest1Dlg类声明一个对象后,立即调用其构造函数(关于构造函数,你需要看相关c++书籍),而这又引起其父类CDialog构造函数的调用。这里要提醒你注意,我们原来的代码是这样的:
在.h中的声明 CTest1Dlg(CWnd* pParent = NULL);
在.cpp中的实现
CTest1Dlg::CTest1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CTest1Dlg::IDD, pParent)
注意到了吗?pParent为NULL。(详细信息请查看MSDN中CDialog::CDialog)
我们着重关注CTest1Dlg::IDD这个参数。IDD从何而来?在头文件中:enum { IDD = IDD_TEST1_DIALOG },你尽可以把名字改掉,例如改成MYIDD:
enum { MYIDD = IDD_TEST1_DIALOG };
CTest1Dlg::CTest1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CTest1Dlg::MYIDD, pParent)
还可以这样:
CTest1Dlg::CTest1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(MYIDD, pParent)
甚至,你可以打开Resource.h找到IDD_TEST1_DIALOG的定义:
#define IDD_TEST1_DIALOG 102
那么你可以这样:
CTest1Dlg::CTest1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(102, pParent)
这说明了什么?说明这个参数不过是对话框模板资源的ID而已,那么我们可以更改这个ID让程序装载另外一个对话框模板资源。在ResourceView 的Dialog文件夹上右键->insert dialog插入一个对话框,它的默认ID是IDD_DIALOG1,我们这样做:
enum { IDD = IDD_DIALOG1 };
CTest1Dlg::CTest1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CTest1Dlg::IDD, pParent)
运行看看。于是我们得到一个启发:在InitInstance()函数里面可以根据不同的条件显示不同的面板。
第6步:
CTest1Dlg对象dlg调用其成员函数DoModal()显示对话框。
第7、8步:
退出程序。
MFC程序的流程如此简单(至少目前我们表面上看到的是这样),你还有什么好怕的呢?学习MFC也很容易啊,呵呵,事实并非如此,下次我会结合F10、F11让你看到更多的东西。
上一篇我们已经分析了MFC的简单执行流程:
////////////////////////////////////////////////////////
Test1.cpp:
#include .....
CTest1App theApp;/*第1步*/
BOOL CTest1App::InitInstance()
{/*第2步*/
CTest1Dlg dlg;/*第3步*/
dlg.DoModal();/*第6步*/
return FALSE;/*第7步*/
}/*第8步*/
////////////////////////////////////////////////////////
Test1Dlg.cpp:
#include .....
CTest1Dlg::CTest1Dlg(CWnd* pParent )
: CDialog(CTest1Dlg::IDD, pParent)
{/*第4步*/
}/*第5步*/
在我进一步分析的时候,发现MFC基于Dialog based的工程竟然是一个胎死腹中的怪胎!证据就是在InitInstance()函数中永远return FALSE,永远初始化失败!这导致我无法继续分析一个较为完整的MFC流程,例如theApp的构造,线程的Run等等。现在暂且简单分析一下这个对话框工程,在下一篇基于SDI的工程里面再好好分析吧!
Come on!
注://zuilang开头的是我加的注释。
现在进一步分析,如上一篇那样加上断点,现在你应该有经验了,知道那些地方可以加断点。好,F5开始(对了,环境是vc6),一直到第4步,然后,换F11(单步执行,碰到子函数进入内部),哈哈,进入MFC源码DLGCORE.CPP(core是什么意思?核心!再次强调英文的重要性)。
CDialog::CDialog(UINT nIDTemplate, CWnd* pParentWnd)
{//光标停在这里了
AFX_ZERO_INIT_OBJECT(CWnd);
m_pParentWnd = pParentWnd;
m_lpszTemplateName = MAKEINTRESOURCE(nIDTemplate);
m_nIDHelp = nIDTemplate;
}
上面这段代码应该不难看懂,就是CDialog的构造函数,再次说明了我们的对话框父类是CDialog,并且也知道了构造函数在什么时候执行。
再按F11,进入WINCORE.CPP:
CWnd::CWnd()
{//光标停在这里
AFX_ZERO_INIT_OBJECT(CCmdTarget);
}
说明CDialog的父类是CWnd。
继续F11,进入CMDTAGE.CPP:
CCmdTarget::CCmdTarget()
{//光标停在这里
// capture module state where object was constructed
#ifdef _AFXDLL
m_pModuleState = AfxGetModuleState();
ASSERT(m_pModuleState != NULL);
#endif
// initialize state
#ifndef _AFX_NO_OLE_SUPPORT
m_dwRef = 1;
m_pOuterUnknown = NULL;
m_xInnerUnknown = 0;
m_xDispatch.m_vtbl = 0;
m_bResultExpected = TRUE;
m_xConnPtContainer.m_vtbl = 0;
#endif
}
呵呵,MFC是不是开始复杂了?就此停住,你有兴趣可以继续深入进去。我们现在使用F10一直到第6步。
再次使用F11,重新进入DLGCORE.cpp,这里不准备详细解释每一句代码,其他问题你可以自己研究(例如:对话框面板资源是如何和程序联系在一起的?你若觉得吃力,除了我注释的代码,其它代码完全可以当它是透明的)
int CDialog::DoModal()
{//光标停在这里
// can be constructed with a resource template or InitModalIndirect
ASSERT(m_lpszTemplateName != NULL || m_hDialogTemplate != NULL ||
m_lpDialogTemplate != NULL);
// load resource as necessary
LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;
HGLOBAL hDialogTemplate = m_hDialogTemplate;
HINSTANCE hInst = AfxGetResourceHandle();
if (m_lpszTemplateName != NULL)
{
hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);
HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
hDialogTemplate = LoadResource(hInst, hResource);
}
if (hDialogTemplate != NULL)
lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);
// return -1 in case of failure to load the dialog template resource
if (lpDialogTemplate == NULL)
return -1;
// disable parent (before creating dialog)
HWND hWndParent = PreModal();
AfxUnhookWindowCreate();
BOOL bEnableParent = FALSE;
if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))
{
::EnableWindow(hWndParent, FALSE);
bEnableParent = TRUE;
}
TRY
{
// create modeless dialog//zuilang:创建非模态(或者非模式,总之是modeless)对话框,你也许觉得很奇怪,明明是模态对话框啊,其实可以这么理解,模态对话框是非模态对话框的一种特殊形式。
AfxHookWindowCreate(this);
if (CreateDlgIndirect(lpDialogTemplate,
CWnd::FromHandle(hWndParent), hInst))
{
if (m_nFlags & WF_CONTINUEMODAL)
{
// enter modal loop //zuilang:模态对话框在这里得到体现
DWORD dwFlags = MLF_SHOWONIDLE;
if (GetStyle() & DS_NOIDLEMSG)
dwFlags |= MLF_NOIDLEMSG;
VERIFY(RunModalLoop(dwFlags) == m_nModalResult);//zuilang:进入模态对话框的循环,按F10到这里后换F11,然后进入WINCORE.CPP,请在这一句后先看本文下面标有“开始循环”的地方。回到这里来^_^,F10直到程序退出。
}
// hide the window before enabling the parent, etc.
if (m_hWnd != NULL)
SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|
SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
}
}
CATCH_ALL(e)
{
DELETE_EXCEPTION(e);
m_nModalResult = -1;
}
END_CATCH_ALL
if (bEnableParent)
::EnableWindow(hWndParent, TRUE);
if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)
::SetActiveWindow(hWndParent);
// destroy modal window
DestroyWindow();
PostModal();
// unlock/free resources as necessary
if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)
UnlockResource(hDialogTemplate);
if (m_lpszTemplateName != NULL)
FreeResource(hDialogTemplate);
return m_nModalResult;
}
开始循环:
int CWnd::RunModalLoop(DWORD dwFlags)
{//光标停在这里
ASSERT(::IsWindow(m_hWnd)); // window must be created
ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);
HWND hWndParent = ::GetParent(m_hWnd);
m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
MSG* pMsg = &AfxGetThread()->m_msgCur;
// acquire and dispatch messages until the modal state is done
for (;;)//zuilang:终于进入窗体的无限循环结构了(到这里你需要windows程序的相关背景知识--消息循环),看上面的英文就可以知道,这是用来处理消息的,即使对于有一定MFC经验的初学者,相信到了这里也会有豁然开朗的感觉。
{
ASSERT(ContinueModal());
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
{
ASSERT(ContinueModal());
// show the dialog when the message queue goes idle
if (bShowIdle)
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
// call OnIdle while in bIdle state
if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
{
// send WM_ENTERIDLE to the parent
::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
}
if ((dwFlags & MLF_NOKICKIDLE) ||
!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
{
// stop idle processing next time
bIdle = FALSE;
}
}
// phase2: pump messages while available
do
{
ASSERT(ContinueModal());
// pump message, but quit on WM_QUIT
if (!AfxGetThread()->PumpMessage())
{
AfxPostQuitMessage(0);
return -1;
}
// show the window when certain special messages rec'd
if (bShowIdle &&
(pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
{
ShowWindow(SW_SHOWNORMAL);
UpdateWindow();
bShowIdle = FALSE;
}
if (!ContinueModal())
goto ExitModal;//zuilang:到了这里,F10是再也出不去了,因为会不断的有消息循环,我们没有机会到对话框上退出程序,在下面ExitModal处加个断点,然后按F5继续运行。
// reset "no idle" state after pumping "normal" message
if (AfxGetThread()->IsIdleMessage(pMsg))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
}
ExitModal:
m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);//zuilang:这里加断点,到这里后按F10继续,一直回到RunModalLoop函数
return m_nModalResult;
}
至此,程序结束,我们发现,基于Dialog based的工程不过是在整个程序实例初始化的过程当中显示了一个窗体而已,这个事实告诉我们:我们可以在这个初始化过程当中显示多个窗体:
BOOL CTest1App::InitInstance()
{
CTest1Dlg dlg;
dlg.DoModal();
CTest1Dlg dlg2;
dlg2.DoModal();
return FALSE;
}
并且,这些窗体(在这里是dlg2)可以是其他对话框类(尽管我在这里演示的仍然是CTest1Dlg类),因此,我们应该可以想到,这个对话框类可以用来做其他事情,例如用户登录对话框。而我,确实就是这么做的。
基于Dialog based的工程暂时就分析到这里,现在,最起码你应该对派生、继承、构造函数等有一个感性认识,现在去翻一翻c++的基础书,相信你的印象会更加深刻。暂时不考虑给它增肥,保留这个工程,等我分析了基于SDI的工程后再来增肥。
如上篇所说,由于MFC基于Dialog based的工程InitInstance()函数中永远return FALSE,导致我们无法继续往下分析,于是现在来分析一下SDI工程。
前面和Dialog based的工程类似,先建立一个名为Sdi的SDI工程。在向导的第三步,把ActiveX Controls的勾去掉;在第四步,也是把所有的勾都去掉,单击Advanced,选择Windows Styles,同样把所有的勾都去掉(从前面对Dialog based工程的分析知道,反正我们最后都是要删掉的)。
工程建立后,如Dialog based工程,删掉相关代码,尽可能多的加断点,但IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd),IMPLEMENT_DYNCREATE(CSdiDoc, CDocument),IMPLEMENT_DYNCREATE(CSdiView, CView)这3句不加(涉及动态创建等东西,目前不适合我们这篇文章,可参考深入浅出MFC),然后F5运行,最后的代码及程序运行步骤如下:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StdAfx.h:
#include <afxwin.h>
#include <afxext.h>
#include <afxdtctl.h>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
StdAfx.cpp:
#include "stdafx.h"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MainFrm.h:
class CMainFrame : public CFrameWnd
{
protected:
CMainFrame();
DECLARE_DYNCREATE(CMainFrame)
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual ~CMainFrame();
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MainFrm.cpp:
#include "stdafx.h"
#include "Sdi.h"
#include "MainFrm.h"
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
CMainFrame::CMainFrame()
{//zuilang:第12步
}//zuilang:第13步
CMainFrame::~CMainFrame()
{//zuilang:第41步
}//zuilang:第42步
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{//zuilang:第14、19步
if( !CFrameWnd::PreCreateWindow(cs) ) //zuilang:第15、20步
return FALSE;
cs.style = WS_OVERLAPPED | WS_CAPTION | FWS_ADDTOTITLE
; //zuilang:第16、21步
return TRUE; //zuilang:第17、22步
}//zuilang:第18、23步
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sdi.h:
#include "resource.h"
class CSdiApp : public CWinApp
{
public:
CSdiApp();
virtual BOOL InitInstance();
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sdi.cpp:
#include "stdafx.h"
#include "Sdi.h"
#include "MainFrm.h"
#include "SdiDoc.h"
#include "SdiView.h"
CSdiApp::CSdiApp()
{//zuilang:第2步
}//zuilang:第3步
CSdiApp theApp;//zuilang:第1步
BOOL CSdiApp::InitInstance()
{//zuilang:第4步
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CSdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CSdiView)); //zuilang:第5步
AddDocTemplate(pDocTemplate); //zuilang:第6步
CCommandLineInfo cmdInfo; //zuilang:第7步
ParseCommandLine(cmdInfo); //zuilang:第8步
if (!ProcessShellCommand(cmdInfo)) //zuilang:第9步
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW); //zuilang:第33步
m_pMainWnd->UpdateWindow();//zuilang:第34步
return TRUE; //zuilang:第37步
}//zuilang:第38步
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SdiDoc.h:
class CSdiDoc : public CDocument
{
protected:
CSdiDoc();
DECLARE_DYNCREATE(CSdiDoc)
public:
virtual BOOL OnNewDocument();
virtual ~CSdiDoc();
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SdiDoc.cpp:
#include "stdafx.h"
#include "Sdi.h"
#include "SdiDoc.h"
IMPLEMENT_DYNCREATE(CSdiDoc, CDocument)
CSdiDoc::CSdiDoc()
{ //zuilang:第10步
} //zuilang:第11步
CSdiDoc::~CSdiDoc()
{//zuilang:第43步
}//zuilang:第44步
BOOL CSdiDoc::OnNewDocument()
{ //zuilang:第29步
if (!CDocument::OnNewDocument())//zuilang:第30步
return FALSE;
return TRUE; //zuilang:第31步
} //zuilang:第32步
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SdiView.h:
class CSdiView : public CView
{
protected:
CSdiView();
DECLARE_DYNCREATE(CSdiView)
public:
virtual void OnDraw(CDC* pDC);
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual ~CSdiView();
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SdiView.cpp:
#include "stdafx.h"
#include "Sdi.h"
#include "SdiDoc.h"
#include "SdiView.h"
IMPLEMENT_DYNCREATE(CSdiView, CView)
CSdiView::CSdiView()
{ //zuilang:第24步
} //zuilang:第25步
CSdiView::~CSdiView()
{//zuilang:第39步
}//zuilang:第40步
BOOL CSdiView::PreCreateWindow(CREATESTRUCT& cs)
{ //zuilang:第26步
return CView::PreCreateWindow(cs); //zuilang:第27步
} //zuilang:第28步
void CSdiView::OnDraw(CDC* pDC)
{//zuilang:第35步,此时去掉这一句的断点
}//zuilang:第36步,此时去掉这一句的断点
大概的流程如下:
构造应用程序类全局对象theApp-》应用程序类初始化InitInstance()-》构造文档CSdiDoc()-》构造主框架CmainFrame()-》准备创建框架窗口CMainFrame::PreCreateWindow-》构造视CSdiView()-》准备创建视窗口CSdiView::PreCreateWindow-》打开新文档CSdiDoc::OnNewDocument()-》显示窗口m_pMainWnd->ShowWindow-》更新窗口m_pMainWnd->UpdateWindow()-》重绘窗口CSdiView::OnDraw-》析构视~CSdiView()-》析构框架~CMainFrame()-》析构文档~CSdiDoc()
MFC应用程序详细的启动过程比上面的要复杂n倍!下一篇进行更深入的分析!
在这一篇我们准备进入MFC源码分析,打开上一篇所建立的工程,不加任何断点,F11开始调试,进入APPMODUL.CPP:
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{//zuilang:光标停在这里,注意,加个断点。
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
然后停止debug,在CSdiApp theApp;这一句加上断点,重新F11开始,你会发现,光标首先停在这一句而不是刚才的那一句,这说明了“在C++程序中,全局对象在主程序被执行之前就已经被构造好了”(visual c++ .net技术内幕第六版P23)。
下面不断继续F11:
CSdiApp::CSdiApp()//zuilang:Sdi.cpp
{// zuilang:光标停在这里。
}
CWinApp::CWinApp(LPCTSTR lpszAppName) //zuilang:APPCORE.CPP
{// zuilang::光标停在这里,theApp父类是CWinApp。
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
……
}
CWinThread::CWinThread()//zuilang:THRDCORE.CPP
{// zuilang:光标停在这里,CwinApp父类是CWinThread。
m_pThreadParams = NULL;
……
}
CCmdTarget::CCmdTarget()//zuilang:CMDTARG.CPP
{// zuilang:光标停在这里,CWinThread父类是CCmdTarget。
……
}
_ AFX_INLINE CObject::CObject()//zuilang:AFX.INL
{// zuilang:光标停在这里,CCmdTarget父类是:CObject。 }
至此,F5;
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{// zuilang:光标停在这里,改为F11往下。
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{// zuilang:光标停在这里,F10往下。
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())//zuilang:光标停在这里,F11回到CSdiApp::InitInstance()。
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
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)
{
TRACE1("Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}
BOOL CSdiApp::InitInstance()
{// zuilang:光标停在这里,F10往下。下面是单文档模板的创建,暂时忽略。
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CSdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CSdiView));
AddDocTemplate(pDocTemplate);
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// zuilang:处理命令行、外壳命令等
if (!ProcessShellCommand(cmdInfo)) // zuilang:光标停在这里,F11.
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
BOOL CWinApp::ProcessShellCommand(CCommandLineInfo& rCmdInfo)
{// zuilang:光标停在这里,F10往下。
BOOL bResult = TRUE;
switch (rCmdInfo.m_nShellCommand)
{
case CCommandLineInfo::FileNew:
if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)) // zuilang:光标停在这里,F11往下,然后马上Shift+F11跳出AfxGetApp()然后再次F11进入OnCmdMsg,MFC越来越复杂^_^。
OnFileNew();
if (m_pMainWnd == NULL)
bResult = FALSE;
break;
……
}
OnCmdMsg函数较长,只贴最后我们关心的:
BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{// zuilang:光标停在这里,找下面那一句。
……………..
#endif //_DEBUG
return _AfxDispatchCmdMsg(this, nID, nCode,
lpEntry->pfn, pExtra, lpEntry->nSig, pHandlerInfo);// zuilang::找到这一句,CTRL+F10运行到这里,F11继续。
}
}
return FALSE; // not handled
}
AFX_STATIC BOOL AFXAPI _AfxDispatchCmdMsg(CCmdTarget* pTarget, UINT nID, int nCode,
AFX_PMSG pfn, void* pExtra, UINT nSig, AFX_CMDHANDLERINFO* pHandlerInfo)
// return TRUE to stop routing
{// zuilang:光标停在这里,找下面那一句。
……………..
switch (nSig)
{
case AfxSig_vv:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
(pTarget->*mmf.pfn_COMMAND)(); // zuilang:找到这一句,CTRL+F10运行到这里,F11继续。
break;
……………..
}
void CWinApp::OnFileNew()
{ // zuilang:终于找到FileNew。
if (m_pDocManager != NULL)
m_pDocManager->OnFileNew();//zuilang:到这里F11。
}
void CDocManager::OnFileNew()
{// zuilang:光标停在这里,找下面那一句。
if (m_templateList.IsEmpty())
{
TRACE0("Error: no document templates registered with CWinApp.\n");
AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);
return;
}
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
if (m_templateList.GetCount() > 1)
{
// more than one document template to choose from
// bring up dialog prompting user
CNewTypeDlg dlg(&m_templateList);
int nID = dlg.DoModal();
if (nID == IDOK)
pTemplate = dlg.m_pSelectedTemplate;
else
return; // none - cancel operation
}
ASSERT(pTemplate != NULL);
ASSERT_KINDOF(CDocTemplate, pTemplate);
pTemplate->OpenDocumentFile(NULL); // zuilang:CTRL+F10到这里F11。
// if returns NULL, the user has already been alerted
}
CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)
// if lpszPathName == NULL => create new file of this type
{// zuilang:光标停在这里,找下面那一句。
……………..
InitialUpdateFrame(pFrame, pDocument, bMakeVisible); // zuilang:CTRL+F10到这里F11。
return pDocument;
}
至此我突然想改变主意,不再继续往下分析,原因有:
A:方法已经很明显,再往下,显得太罗嗦。
B:最困难的不是里面的知识点,而是要把它们很简单的说出来。很简单的说出来,让人很容易明白,这正是我的初衷,现在已不太可能。
同时,也放弃一开始说的逐句代码添加回去直到和删除代码前一模一样,以便分析每一行代码的作用的想法,因为我觉得到了现在已经不是一件什么难事。
下面用另外一个方法结尾,新建一个SDI工程(用一开始备份的也行),利用ClassWizard对CMainFrame添加如下函数:
1, BOOL CMainFrame::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
2, nt CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
3, BOOL CMainFrame::DestroyWindow()
4, void CMainFrame::OnDestroy()
5, BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
对CSdiApp添加如下函数:
1, BOOL CSdiApp::InitApplication()
2, int CSdiApp::ExitInstance()
对CsdiView添加如下函数:
1, BOOL CSdiView::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
2, void CSdiView::OnInitialUpdate()
3, BOOL CSdiView::DestroyWindow()
4, int CSdiView::OnCreate(LPCREATESTRUCT lpCreateStruct)
5, void CSdiView::OnDestroy()
通过增加上述函数(其实就是重载,放到自己的代码里,看得更清楚),然后再用“断点法”,你也许更容易发现MFC的一般流程。
初学者分析MFC代码,END!!!