MFC框架程序剖析笔记(上篇)

参考:《VC++深入详解》、网络资料

使用IDE:VS2013

MFC是什么?

      微软基础类库(Microsoft Foundation Classes,简称MFC),它以C++类的形式封装了大部分Windows API同时也是一个框架。你应该试过,在vc++里新建一个MFC的工程,开发环境会自动帮你产生许多文件,同时它使用了mfcxx.dll (xx是版本),它封装了mfc内核,所以你在你的代码看不到原本的SDK编程中的消息循环等东西,因为MFC框架帮你封装好了,这样你就可以专心的考虑你程序的逻辑,而不是这些每次编程都要重复的东西,但是由于是通用框架,没有最好的针对性,当然也就丧失了一些灵活性和效率但是MFC的封装很浅,所以效率上损失不大。



     所以说MFC是对大部分的WindowsAPI进行了封装,用起来要比Windows SDK编程方便一些。但初学MFC,会有些感觉云里雾里,主函数WinMain在哪,原先SDK编程的那套框架封装到哪去了?


     用VS2013新建一个MFC应用程序项目,取名Test,选择单文档,文档/视图支持,MFC标准程序即可。

     有CWinApp、CMainFrame、CTestView、CTestDoc、CAboutDlg五个类。框架程序执行主要需要前两个类。

  


1.寻找WinMain

      但实际上我们发现在MFC框架中找不到WinMain(编辑---查找和替换---在文件中查找或使用快捷键CTRL+F,在整个解决方案中查找),甚至连WNDCLASS、CreateWindow等一些设计窗口类、创建窗口的代码也无法找到,显然MFC是不可能没有使用它们的,我们之所以没有找到它们,是因为它们是每一个Win32应用程序都需要的步骤,为了简化程序员的开发工作,都被封装在了MFC的底层框架类中。

      WinMain函数现在是当程序编译链接时,由链接器将该函数链接到Test程序中,再来调用一些方法,这样WinMain和CMainFrame、CTestApp这几个类就有了联系。


     在…Microsoft Visual Studio12.0\VC\atlmfc\src\mfc下找到appmodul.cpp,打开并找到如下代码:

extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,   //#define _tWinMain WinMain, 可以右键--转到定义,验证
    _In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{                                //在此处按F9设立断点一,进行调试会发现应用程序显示窗口前确实会进入WinMain函数
    // callshared/exported WinMain
    return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}


      设立断点进行调试是一个不错的观察程序运行过程的方法,如果调试结束时报了一些关于无法加载或打开pdb文件的错误,可以点开调试---选项和设置---常规---启用源服务器支持---勾选,符号---Mircosoft符号服务器---勾选,耐心等待加载,就行了,也可以眼不见为净,暂时没有发现会影响调试。


-------------------------------------------------------------------------------------------------------------------------------------------------------------

      找到了WinMain,我们现在应该关注的是WinMain是如何与MFC程序中的各个类关联起来的呢?

      我们知道,Win32应用程序的实例是由实例句柄(hInstance)来标识的。

      而对于MFC来说,它通过产生一个应用程序类(CWinApp)的对象(theApp)来唯一标识应用程序的实例。

      每一个MFC程序有且仅有一个从应用程序类派生的类。每一个MFC程序实例有且仅有一个该派生类的实例化对象。

      所以这个唯一的theApp全局对象就标识了应用程序本身,从它来入手观察。

-------------------------------------------------------------------------------------------------------------------------------------------------------------



2.验证CTestApp建立的对象theApp是全局对象


      我们找到CTestApp类(派生于CWinApp类),转到其构造函数定义处:

CTestApp::CTestApp()
{       //断点二                                                                                            
	// 支持重新启动管理器
	m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
#ifdef _MANAGED
	// 如果应用程序是利用公共语言运行时支持(/clr)构建的,则: 
	//     1) 必须有此附加设置,“重新启动管理器”支持才能正常工作。
	//     2) 在您的项目中,您必须按照生成顺序向 System.Windows.Forms 添加引用。
	System::Windows::Forms::Application::SetUnhandledExceptionMode(System::Windows::Forms::UnhandledExceptionMode::ThrowException);
#endif

	// TODO:  将以下应用程序 ID 字符串替换为唯一的 ID 字符串;建议的字符串格式
	//为 CompanyName.ProductName.SubProduct.VersionInformation
	SetAppID(_T("Test.AppID.NoVersion"));

	// TODO:  在此处添加构造代码,
	// 将所有重要的初始化放置在 InitInstance 中
}

// 唯一的一个 CTestApp 对象

CTestApp theApp;      //断点三

      调试---运行顺序为断点三----断点二---断点一,可见theApp对象确实是在WinMain外部定义的全局对象。


-------------------------------------------------------------------------------------------------------------------------------------------------------------

      CTestApp类派生于CWinApp类,我们知道,建立派生类对象时应先调用基类构造函数,才能调用派生类构造函数。

      这样一来,子类就与微软提供的CWinApp关联起来了,通过这样的方式,MFC中大量的类之间就有了关联。

      现在我们去了解CWinApp类~


-------------------------------------------------------------------------------------------------------------------------------------------------------------




3.初窥CTestApp的基类CWinApp

        现在开始寻找CWinApp类的构造函数定义,在…Microsoft Visual Studio 12.0\VC\atlmfc\src\mfc下找到appcore.app并打开,找到其构造函数,发现CWinApp的构造函数是有形参的(LPCTSTR lpszAppName)。

CWinApp::CWinApp(LPCTSTR lpszAppName)
{
	if (lpszAppName != NULL)
		m_pszAppName = _tcsdup(lpszAppName);
	else
		m_pszAppName = NULL;

	// initialize CWinThread state
	AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
	ENSURE(pModuleState);
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
	ENSURE(pThreadState);
	ASSERT(AfxGetThread() == NULL);
	pThreadState->m_pCurrentWinThread = this;
	ASSERT(AfxGetThread() == this);
	m_hThread = ::GetCurrentThread();
	m_nThreadID = ::GetCurrentThreadId();

	// initialize CWinApp state
	ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
	pModuleState->m_pCurrentWinApp = this;      //根据C++继承性原理这里的this对象代表的是子类CTestApp的对象                                         ASSERT(AfxGetApp() == this);

	// in non-running state until WinMain
......
 
 不过转到CWinApp类中构造函数的声明你会发现,这个形参是有默认值NULL的,所以在子类CTestApp的构造函数没有显式地调用父类带参数的构造函数。 
 

class CWinApp : public CWinThread
{
	DECLARE_DYNAMIC(CWinApp)
public:

// Constructor
	explicit CWinApp LPCTSTR lpszAppName = NULL  // app name defaults to EXE name                                                           ......

另外需要解释的是:在CWinApp类的构造函数中this对象代表的是CTestApp类的theApp对象。

pModuleState->m_pCurrentWinApp = this;      //根据C++继承性原理这里的this对象代表的是子类CTestApp的对象 

 
 

这里在新建的控制台项目的cpp中拟写了以下验证程序:

#include <iostream>
using namespace std;
class father
{
public:
	father* p1;
	father()
	{
		p1=this;     //用指向基类的指针p1存放this对象
	}
	virtual void func()
	{
		cout<<"father"<<endl;
	}
};

class son: public father
{
public:
	father* p2;
	son()
	{
		p2=p1;        //把存放的this对象传给指向基类的指针p2,定义的对象是g_s,所以主函数内可以调出来的数据成员都是g_s对象的
	}
	virtual void func()
	{
		cout<<"son"<<endl;
	}
};
 
son g_s;         //定义子类全局对象g_s

int main()
{

    g_s.p2->func();
    return 0;
}
输出结果是son,这就证明了定义son类全局对象g_s时在father类构造函数中this指针存放的是son类全局对象g_s的地址(虽然没有调用son类构造函数,但是对象存放地址已经开辟出来了)。

还有一种我觉得不错的验证的方法就是设置断点,在全局对象定义处、父类构造函数处、子类构造函数处、主函数接口处。

然后进行调试,调试同时注意观察下方窗口中变量值的变化,可以很清楚的看见在我的测试程序中运行至father类构造函数p1=this;时this指针把全局对象g_s的值传给了p1。


-------------------------------------------------------------------------------------------------------------------------------------------------------------

至此,我们明白了下面3件事情:我们定义了全局对象theApp,而定义theApp需要调用CTestApp类的构造函数和基类CWinApp的构造函数,当构造完theApp后,进入WinMain函数,

根据前面_tWinMain函数的定义,WinMain函数实际上是通过调用AfxWinMain函数来实现的,这个函数的定义在WINMAIN.CPP中。


补充:在MFC中,以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们。

-------------------------------------------------------------------------------------------------------------------------------------------------------------

你可能感兴趣的:(MFC框架程序剖析笔记(上篇))