参考:《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; //断点三
-------------------------------------------------------------------------------------------------------------------------------------------------------------
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为前缀的函数都是全局函数,可以在程序的任何地方调用它们。
-------------------------------------------------------------------------------------------------------------------------------------------------------------