MFC程序中的WinMain函数

读者还记得我们在第2章中讲述的创建Win32应用程序的几个步骤吗?当时,我们介绍Win32应用程序有一条很明确的主线:首先进入WinMain函数,然后设计窗口类、注册窗口类、产生窗口、注册窗口、显示窗口、更新窗口,最后进入消息循环,将消息路由到窗口过程函数中去处理。遵循这条主线,我们在写程序时就有了一条很清晰的脉络。

  但在编写MFC程序时,我们找不到这样一条主线,甚至在程序中找不到WinMain函数。可以在当前Test工程中查找WinMain函数,方法是在VC++开发环境中单击【Edit】菜单,选择【Find in Files…】菜单项,并在弹出的查找对话框中“Find What:”文本框内输入“WinMain”,单击【Find】按钮,结果当然是找不到WinMain函数。读者可以在这个工程中,再查找一下WNDCLASS、CreateWindow等,你会发现仍然找不到。那么是不是MFC程序就不需要WinMain函数,不需要设计窗口类,不需要创建窗口了呢?当然不是。我们之所以看不见这些,是因为微软在MFC的底层框架类中封装了这些每一个窗口应用程序都需要的步骤,目的主要是为了简化程序员的开发工作,但这也给我们在学习和掌握MFC程序时造成了很多不必要的困扰。

  为了更好地学习和掌握基于MFC的程序,有必要对MFC的运行机制,以及封装原理有所了解。在第1章就讲述了WinMain函数是所有Win32程序的入口函数,就像DOS下的main函数一样。我们创建的这个MFC程序也不例外,它也有一个WinMain函数,但这个WinMain函数是在程序编译链接时,由链接器将该函数链接到Test程序中的。

  在安装完Microsoft Visual Studio 6.0后,在安装目录下(将Microsoft Visual Studio 6.0安装到了D:Program Files下),微软提供了部分MFC的源代码,我们可以跟踪这些源代码,来找出程序运行的脉络。机器上MFC源代码的具体路径为D:Program FilesMicrosoft Visual StudioVC98MFC SRC,读者可以根据这个目录结构在自己机器上查找相应的目录。找到相应的目录后,在资源浏览器的工具栏上选择“搜索”。然后在搜索窗口的“包含文字”文本框中输入”WinMain”,单击“立即搜索(S)”按钮,搜索结果如图3.12所示。

 

  图3.12 包含“WinMain”文字的搜索结果

  我们只需要查看后缀名为CPP的源文件即可,实际上,WinMain函数在APPMODUL.CPP这个文件中。保持Test工程的打开状态,然后双击APPMODUL.CPP即可在VC++环境中打开该文件,在其中可以找到如例3-1所示的这段代码。

  例3-1

  extern "C" int WINAPI
  _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPTSTR lpCmdLine, int nCmdShow)
  {
  // call shared/exported WinMain
  return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
  }

  WinMain函数找到了。现在我们可以看看Test程序是否会进入这个WinMain函数。在WinMain函数中按下F9键设置一个断点,然后按下F5键调试运行当前程序。我们发现程序确实运行到该断点处停了下来,如图3.13所示。这说明Test这个MFC程序确实有WinMain函数,在程序编译链接时,WinMain函数就成为该程序的一部分。

 

  图3.13 程序运行到WinMain断点处

  但这个_tWinMain函数和第1章所讲的WinMain函数有些不同,让我们先看看这个函数的定义。读者可以在_tWinMain上单击鼠标右键,从弹出的快捷菜单中选择【Go To Definition Of _tWinMain】菜单项,光标就会定位到_tWinMain函数的定义处,代码如例3-2所示,从中我们可以发现_tWinMain实际上是一个宏,展开之后就是WinMain函数。

  例3-2

  #define _tmain main
  #define _tWinMain WinMain
  #ifdef _POSIX_
  #define _tenviron environ
  #else
  #define _tenviron _environ
  #endif
  #define __targv __argv

  1.theApp全局对象

  找到了WinMain函数,那么它是如何与MFC程序中的各个类组织在一起的呢?也就是说,MFC程序中的类是如何与WinMain函数关联起来的呢?

  双击ClassView标签页中的CTestApp类,跳转到该类的定义文件(Test.h)中。可以发现CTestApp派生于CWinApp类,后者表示应用程序类。我们在ClassView标签页中打开CTestApp类前面的“+”符号,双击该类的构造函数,就跳转到该类的源文件(Test.cpp)中。在CTestApp构造函数处设置一个断点,然后调试运行Test程序,将发现程序首先停在CTestApp类的构造函数处,继续运行该程序。这时程序才进入WinMain函数,即停在先前我们在WinMain函数中设置的断点处。

  在我们通常的理解当中,WinMain函数是程序的入口函数。也就是说,程序运行时首先应该调用的是WinMain函数,那么这里为什么程序会首先调用CTestApp类的构造函数呢?看一下CTestApp的源文件,可以发现程序中定义了一个CTestApp类型的全局对象:theApp。代码如下。

  // The one and only CTestApp object

  CTestApp theApp;

  提示:MFC程序的全局变量都放置在ClassView标签页的Globals分支下,展开该分支即可看到程序当前所有的全局变量。双击某个全局变量,即可定位到该变量的定义处。

  我们在这个全局对象定义处设置一个断点,然后调试运行Test程序,将发现程序执行的顺序依次是:theApp全局对象定义处、TestApp构造函数,然后才是WinMain函数。

  为了更好地解释这一过程,我们再新创建一个Win32控制台工程。单击【File】菜单,选择【New】菜单项, 在Projects选项卡下,选择Win32 Console Application类型,在右侧的Project name文本框中输入工程名:main,并将程序放置到适当的位置(即设置Location的内容),如图3.14所示。

 

  图3.14 新建Win32控制台应用

  单击【OK】按钮,进入“Win32 Console Application”向导,选择一个空工程即可,如图3.15所示。单击【Finish】按钮,向导就自动生成一个空的Win32控制台应用框架。

  接着为这个main工程新建一个源文件,方法是单击【File】菜单,选择【New】命令,在弹出的【New】对话框中选择【Files】选项卡,然后选择C++ Source File项,并在右侧的【File】文本框中输入源文件名:main,如图3.16所示。

 

  图3.15 Win32 Console Application向导

 

  图3.16 新建一个源文件

  接下来,在main.cpp文件中输入如例3-3所示的代码。

  例3-3

  #include
int a=6;
void main()
{
cout<
}

  上述代码非常简单,首先定义了一个int类型的全局变量a,并给它赋了一个初值6。然后定义了一个main函数,该函数所做的工作就是将全局变量a的值输出到标准输出cout上。因为使用了标准输出,所以需要包含相应的头文件:iostream.h,这是C++中的标准输入输出流头文件。

  我们在main函数处设置一个断点,调试运行该程序,将会发现程序在进入main函数时,a的值已经是6了。也就是说,在程序入口main函数加载时,系统就已经为全局变量或全局对象分配了存储空间,并为它们赋了初始值。

  小技巧:在程序运行过程中,如果想要查看某个变量的当前值,方法一是把鼠标移到该变量上,停留片刻,VC++就会弹出一个小窗口,此窗口中显示了该变量的当前值,如图3.17所示。

 

  图3.17 显示当前变量取值的小窗口

  方法二是利用VC++提供的调试窗口来查看变量的当前值。操作步骤是单击View菜单,选择Debug Windows选项,在下拉菜单中选择Variables菜单项,即可显示变量窗口,如图3.18所示。该窗口显示了程序当前上下文中的一些重要变量的当前值。

 

 

接下来,把全局变量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 initial

izations
  ③ if (!pThread->InitInstance())
    {
      if (pThread->m_pMainWnd != NULL)
      {
        TRACE0("Warning: Destroying non-NULL m_pMainWndn");
        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全局对象。

  再回到例3-7所示的AfxWinMain函数,可以看到在接下来的代码中,pThread和pApp调用了三个函数(加灰显示的代码行),这三个函数就完成了Win32程序所需要的几个步骤:设计窗口类、注册窗口类、创建窗口、显示窗口、更新窗口、消息循环,以及窗口过程函数。pApp首先调用InitApplication函数,该函数完成MFC内部管理方面的工作。接着,调用pThread的InitInstance函数。在Test程序中,可以发现从CWinApp派生的应用程序类CTestApp也有一个InitInstance函数,其声明代码如下所示。

  virtual BOOL InitInstance();

  从其定义可以知道,InitInstance函数是一个虚函数。根据类的多态性原理,可以知道AfxWinMain函数这里实际上调用的是子类CTe

stApp的InitInstance函数(读者可以在此函数处设置一个断点,并调试运行程序以验证一下)。CTestApp类的InitInstance函数定义代码如例3-9所示。
  例3-9

 BOOL CTestApp::InitInstance()
  {
    AfxEnableControlContainer();
    // Standard initialization
    // If you are not using these features and wish to reduce the size
    // of your final executable, you should remove from the following
    // the specific initialization routines you do not need.
  #ifdef _AFXDLL
    Enable3dControls();     // Call this when using MFC in a shared DLL
  #else
    Enable3dControlsStatic();  // Call this when linking to MFC statically
  #endif
    // Change the reGIStry key under which our settings are stored.
    // TODO: You should modify this string to be something appropriate
    // such as the name of your company or organization.
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));
    LoadStdProfileSettings(); // Load standard INI file options (including MRU)
    // Register the application's document templates. Document templates
    // serve as the connection between documents, frame Windows and views.
    CSingleDocTemplate* pDocTemplate;
  ① pDocTemplate = new CSingleDocTemplate(
      IDR_MAINFRAME,
      RUNTIME_CLASS(CTestDoc),
      RUNTIME_CLASS(CMainFrame),    // main SDI frame window
      RUNTIME_CLASS(CTestView));
    AddDocTemplate(pDocTemplate);
    // Parse command line for standard shell commands, DDE, file open
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);
    // Dispatch commands specified on the command line
    if (!ProcessShellCommand(cmdInfo))
      return FALSE;
    // The one and only window has been initialized, so show and update it.
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();
    return TRUE;
  }


文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/3_program/vc/vcxl/20100630/264882.html

文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/3_program/vc/vcxl/20100630/264883.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值