17xie > VC++深入详解 > 3.2.1 MFC程序中的WinMain函数
背景:                 
[本书目录] [图书首页] [本书讨论区]  
链接地址:http://www.17xie.com/read-36304.html    注册17xie 一起来写书 实现您的出书梦想!

3.2.1  MFC程序中的WinMain函数

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

但在编写MFC程序时,我们找不到这样一条主线,甚至在程序中找不到WinMain函数。可以在当前Test工程中查找WinMain函数,方法是在VC++开发环境中单击【Edit】菜单,选择【Find in Files…】菜单项,并在弹出的查找对话框中“Find What:”文本框内输入“WinMain”,单击【Find】按钮,结果当然是找不到WinMain函数。读者可以在这个工程中,再查找一下WNDCLASSCreateWindow等,你会发现仍然找不到。那么是不是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 Files\Microsoft Visual Studio\VC98\MFC\ 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

1theApp全局对象

找到了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 <iostream.h>

 

int a=6;

 

void main()

{

    cout<<a<<endl;

}

上述代码非常简单,首先定义了一个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所示。该窗口显示了程序当前上下文中的一些重要变量的当前值。

3.18  Variables窗口

接下来,把全局变量a换成一个全局对象,看看结果如何。修改如例3-3所示的代码,新定义一个CPoint类,并定义该类的一个全局变量pt,结果如例3-4所示。

3-4

1#include <iostream.h>

 

2//int a=6;

 

3class CPoint

4{

5public:

6CPoint()

7{

8}

9};

 

10CPoint pt;

11void 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类的构造函数时,就不用显式地去传递这个参数的值。

2AfxWinMain函数

当程序调用了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-8AfxGetThread函数的源代码,位于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函数中的pThreadpApp这两个指针是一致的。

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程序来说,pThreadpApp所指向的都是CTestApp类的对象,即theApp全局对象。

3InitInstance函数

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

virtual BOOL InitInstance();

从其定义可以知道,InitInstance函数是一个虚函数。根据类的多态性原理,可以知道AfxWinMain函数这里实际上调用的是子类CTestAppInitInstance函数(读者可以在此函数处设置一个断点,并调试运行程序以验证一下)。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;

}


字数:14801    最后更新:8个月以前 [03-13 21:43]happyskynet 修改
本页编辑者:happyskynet  
[前一页]:3.2 基于MFC的程序框…  [后一页]:3.2.2 MFC框架窗口
[在本页中加入书签] [收藏本书] [推荐本书]
  17xie论坛 > 本书讨论区 > 本页评论   (共0条)
发表评论

用户名称 匿名发表
评论内容
验证码

关于我们 | 版权声明 | 免责声明 | 诚聘英才 | 联系我们 | 合作伙伴 | 友情链接 | 广告合作 | 提交意见
Copyright © 2007 17xie.com 互联网协同写书平台 京ICP备08002671号