📄 03.2.1 mfc程序中的winmain函数(2).txt
字号:
接下来,把全局变量a换成一个全局对象,看看结果如何。修改如例3-3所示的代码,新定义一个CPoint类,并定义该类的一个全局变量pt,结果如例3-4所示。
例3-4
1.#include <iostream.h>
2.//int a=6;
3.class CPoint
4.{
5.public:
6.CPoint()
7.{
8.}
9.};
10.CPoint pt;
11.void main()
12.{
13.// cout<<a<<endl;
14.}
设置三个断点:CPoint构造函数处(第6行代码处)、pt全局对象定义处(第10行代码处)和main函数定义处(第12行代码处)。选择调试运行main函数,将会看到程序代码执行的先后顺序。这时我们将发现main程序首先到达pt全局对象定义处(第10行代码处);继续运行程序,程序到达CPoint类的构造函数(第6行代码处);再继续运行程序,程序到达main函数处(第12行代码处)。由此可见,无论全局变量,还是全局对象,程序在运行时,在加载main函数之前,就已经为全局变量或全局对象分配了内存空间。对一个全局对象来说,此时就会调用该对象的构造函数,构造该对象,并进行初始化操作。
至此,读者应该明白了先前创建的Test程序的运行顺序,也就是为什么全局变量theApp的构造函数会在WinMain函数之前执行了。那么,为什么要定义一个全局对象theApp,让它在WinMain函数之前执行呢?该对象的作用是什么呢?
先关闭main工程,返回Test程序,并使其处于编辑状态。在前面介绍Win32 SDK应用程序时,曾经讲过应用程序的实例是由实例句柄(WinMain函数的参数hInstance)来标识的。而对MFC程序来说,通过产生一个应用程序类的对象来惟一标识应用程序的实例。每一个MFC程序有且仅有一个从应用程序类(CWinApp)派生的类。每一个MFC程序实例有且仅有一个该派生类的实例化对象,也就是theApp全局对象。该对象就表示了应用程序本身。
我们在第2章中阐述了子类构造函数的执行过程,当一个子类在构造之前会先调用其父类的构造函数。因此theApp对象的构造函数CTestApp在调用之前,会调用其父类CWinApp的构造函数,从而就把我们程序自己创建的类与Microsoft提供的基类关联起来了。CWinApp的构造函数完成程序运行时的一些初始化工作。
下面让我们看看CWinApp类构造函数的定义。像前面搜索“WinMain”函数那样,找到Microsoft提供的CWinApp类定义的源文件:appcore.cpp,并在编辑环境中打开,其中CWinApp构造函数的代码如例3-5所示。
例3-5
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();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
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;
ASSERT(AfxGetApp() == this);
// in non-running state until WinMain
m_hInstance = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_pRecentFileList = NULL;
m_pDocManager = NULL;
m_atomApp = m_atomSystemTopic = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
// initialize wait cursor state
m_nWaitCursorCount = 0;
m_hcurWaitCursorRestore = NULL;
// initialize current printer state
m_hDevMode = NULL;
m_hDevNames = NULL;
m_nNumPreviewPages = 0; // not specified (defaults to 1)
// initialize DAO state
m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called
// other initialization
m_bHelpMode = FALSE;
m_nSafetyPoolSize = 512; // default size
}
上述CWinApp的构造函数中有这样一句代码:
pModuleState->m_pCurrentWinApp = this;
根据C++继承性原理,这个this对象代表的是子类CTestApp的对象,即theApp。同时,可以发现CWinApp的构造函数有一个LPCTSTR类型的形参:lpszAppName。但是我们程序中CTestApp的构造函数是没有参数的。在第2章介绍C++编程知识时,曾经介绍,如果基类的构造函数带有一个形参,那么子类构造函数需要显式地调用基类带参数的构造函数。那么,为什么我们程序中的CTestApp构造函数没有这么做呢?
我们知道,如果某个函数的参数有默认值,那么在调用该函数时可以传递该参数的值,也可以不传递,直接使用默认值即可。我们可以在例3-5所示代码中的CWinApp类名上单击鼠标右键,利用【Go to Definition of CWinApp】命令,定位到CWinApp类的定义处,代码如例3-6所示。
例3-6
class CWinApp : public CWinThread
{
DECLARE_DYNAMIC(CWinApp)
public:
// Constructor
CWinApp(LPCTSTR lpszAppName = NULL); // app name defaults to EXE name
……
从例3-6所示代码中,可以看到CWinApp构造函数的形参确实有一个默认值(NULL)。这样,在调用CWinApp类的构造函数时,就不用显式地去传递这个参数的值。
2.AfxWinMain函数
当程序调用了CWinApp类的构造函数,并执行了CTestApp类的构造函数,且产生了theApp对象之后,接下来就进入WinMain函数。根据前面例3-1所示代码,可以发现WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。
知识点 Afx前缀的函数代表应用程序框架(Application Framework)函数。应用程序框架实际上是一套辅助我们生成应用程序的框架模型。该模型把多个类进行了一个有机的集成,可以根据该模型提供的方案来设计我们自己的应用程序。在MFC中,以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们。
我们可以采取同样的方式查找定义AfxWinMain函数的源文件,在搜索到的文件中双击WINMAIN.CPP,并在其中找到AfxWinMain函数的定义代码,如例3-7所示。
例3-7
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
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())
{
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;
}
在例3-7所示的代码中,AfxWinMain首先调用AfxGetThread函数获得一个CWinThread类型的指针,接着调用AfxGetApp函数获得一个CWinApp类型的指针。从MFC类库组织结构图(读者可以按照前面介绍的方法在MSDN中找到该结构图)中,可以知道CWinApp派生于CWinThread。例3-8是AfxGetThread函数的源代码,位于THRDCORE.CPP文件中。
例3-8
CWinThread* AFXAPI AfxGetThread()
{
// check for current thread in module thread state
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
CWinThread* pThread = pState->m_pCurrentWinThread;
// if no CWinThread for the module, then use the global app
if (pThread == NULL)
pThread = AfxGetApp();
return pThread;
}
从例3-8所示代码中可以发现,AfxGetThread函数返回的就是AfxGetApp函数的结果。因此,AfxWinMain函数中的pThread和pApp这两个指针是一致的。
AfxGetApp是一个全局函数,定义于AFXWIN1.INL中:
_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
{ return afxCurrentWinApp; }
而afxCurrentWinApp的定义位于AFXWIN.H文件中,代码如下:
#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp
我们返回去看看前面例3-5所示的CWinApp构造函数代码,就可以知道AfxGetApp函数返回的是在CWinApp构造函数中保存的this指针。对Test程序来说,这个this指针实际上指向的是CTestApp的对象:theApp。也就是说,对Test程序来说,pThread和pApp所指向的都是CTestApp类的对象,即theApp全局对象。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -