现在的位置: 首页 > 综合 > 正文

《深入浅出MFC》读书笔记(十一)

2012年09月25日 ⁄ 综合 ⁄ 共 8003字 ⁄ 字号 评论关闭

第6章 程序的生死因果

 

 这一部分应该是第一章某些章节的扩展,难怪当时看第一章觉得解释不够详细,原来侯捷把大头放在这一章了。前面看到第一章时,还特意找了份关于SDK程序的材料来看,估计现在看这一章会轻松一点。

 需要什么函数库?
     1.Windows C Runtime函数库(如LIBC.LIB,MSVCRT.LIB,MSVCRTD.LIB)
     2.DLL Import函数库(如GDI32.LIB,USER32.LIB,KERNEL32.LIB)
     3.MFC函数库(AFX函数库),即MFC这个appliction framework的本体,可以静态
       链接(如MFC42.DLL,MFC42D.DLL),也可以动态链接(用对应的MFC import函数库
       代替,如MFC42.LIB,MFC42D.LIB)
     以上工作,可以由IDE自动完成。

 需要什么头文件
     
 如果是SDK程序,只要载入WINDOWS.H就可以了;
 如果是MFC程序,则还需要其他一些头文件:
     StdAfx.h:用来作为预编译头文件,其内只是载入其他的MFC头文件。用VC自动生成
              的程序就能看到该文件,下面提到的几个头文件就在此文件中included
              进来。
     AfxWin.h:每个MFC程序必须载入此头文件(出现在上面那个头文件中),该文件声
              明了所有的MFC类。AfxWin.h内含Afx.h,后者又载入AfxVer_.h,后者又
              载入Windows.h,即Windows.h这个SDK函数必须载入的头文件在MFC中由
              AfxWin.h间接载入。
     AfxExt.h:凡使用工具栏,状态栏的程序必须载入。
     AfxDlgs.h:使用通用对话框的程序必须载入。
     AfxColl.h:凡使用Collections classes的程序必须载入。
     AfxRes.h :MFC的rc文件必须载入此文件。标准资源的ID都有默认值,都定义于此 
               如:  
                    #define ID_FILE_NEW   0xE100
                    #define ID_FILE_OPEN  0xE101
                    … …
               这些菜单项都有文字说明(出现在状态栏),文字说明在定作框架程序
               时才把说明文字加到程序的RC文件中。
            
 为什么需要预编译头文件(Precompiled Header)?
     一个程序在编写过程中需要经常编译,但Windows程序的标准头文件非常巨大,且内容不变,如果每次都要重新编译要浪费很多时间,而且也没有必要。预编译可以将头文件第一次编译后的结果存起来,以后就可以直接从磁盘取出来用。

 简化的MFC程序结构

 MFC将传统Win32程序中所调用的WinAPI函数很好地封装进不同的类中,甚至连SDK中几大基本函数都被封装进去。此部分就是通过一个简单的win32程序来看看MFC程序的基本结构。

 一个SDK程序,其主体在与WinMain和WndProc函数,而这两部分在所有的程序中其框架都是不变的,于是,MFC把这两个具有相当固定行为的函数封装至两个类中:

 WinMain内部操作被封装在CWinApp中,CWinApp代表程序本体;
 WndProc内部操作被封装在CFrameWnd中,CFrameWnd代表一个窗口。

 当然,每个程序中WinMain和WndProc部分的操作不可能完全相同,所以我们必须以上面两个类为基础,派生出自己的类,并改写一部分成员函数。

 下面我们具体的看看这两个类:

 CWinApp—-取代WinMain的地位

 从CWinApp派生出来的对象被称为application object,即为程序的本体。一个程序的本体包含了很多内容,如如与程序有关而与窗口无关的数据或者动作,命令行参数,三InitApplication 和InitInstance操作等都包含在内,即CWinApp必须有以上的成员变量以及成员函数。

 SDK中要做的都由CWinApp中的三个成员函数完成:
    virtual BOOL InitApplication();
    virtual BOOL InitInstance();
    virtual int Run();

 这三块由一个类似于WinMain的函数来驱动。

 CFrameWnd—-取代WndProc的地位

 传统的SDK程序主要是用一个swith-case语句来处理窗口收到的消息;MFC程序有新的做法,如在这里的Hello程序中,CMyFrameWnd处理两个消息,具有如下的声明:
     public:
       afx_msg void OnPain();
       afx_msg void OnAbout();
       DECLARE_MESSAGE_MAP() //该宏用来实现消息与处理函数的关联 
       … … 

 Application object—-引爆器

 在每个MFC程序中,都有且只有一个application object,如在HELLO.CPP中,有如下声明:
      CMyWinApp  theApp;

 当程序运行到此处时,会调用CMyWinApp的构造函数,由于我们没有定义该够函数,所以调用其父类的构造函数,以用来完成一些初始化工作和配置工作,如置CWinApp一些成员变量为NULL,指定m_pCurrentWinApp为this,等等。

 隐晦不明的WinMain

 在全局变量theApp配置好后,才开始进入WinMain函数,但是该函数并没有出现在我们的自己撰写的代码中。事实上,这部分代码是由连接器直接加到我们的应用程序中去的。

 在一个名为_tWinMain的函数中,调用了AfxWinMain函数。AfxWinMain函数定义于WinMain.cpp中,其主要任务是:

      int AfxWinMain(….){
          CWinApp* pApp=AfxGetApp();
          AfxWinInit(….);          //CWinApp构造后的第一个操作—-初始化

          pApp->InitApplication();   //a
          pApp->InitInstance();      //b
          nReturnCode=pApp->Run();   //c
  
          AfxWinTerm();
          return nReturnCode;
      }

 注意a,b,c处,继承,虚拟,对象指针等知识的应用。

 以下分别对AfxWinMain内几个函数作介绍:

 1.AfxWinInit—-AFX内部初始化操作

 
 AfxWinInit是CWinApp构造函数之后的第一个操作,主要步骤:
 1.取到当前的全局变量theApp地址;
 2.对该全局变量初始化(该全局变量在刚构造出来时,基本上是初始化为NULL了,而
   此处赋予了具体的值);

 值得注意的是,以前版本的VC中,WinMain一开始就调用AfxWinInit,注册四个窗口类,而现在这四个窗口类不在AfxWinInit中完成。

 2.CWinApp::InitApplication    

 pApp->InitApplication,虽然pApp是CWinApp类,但实际调用的是CMyWinApp::InitApplication,但又因为,CMyWinApp继承自CWinApp时,通常不需要该改写InitApplication函数,故最终仍调用CWinApp::InitApplication()。

 InitApplication中所作操作都是为了MFC的内部管理而作的。

 3. CMyWinApp::InitInstance

 pApp->InitInstance(),最终调用的是CMyWinApp::InitInstance(),因为CWinApp中该函数是个空函数,应用程序一定要改写该函数,即此部分代码要我们写好(如Hello.cpp中)。

 通常,CWinApp中的InitApplication和Run都不需要改写,而InitInstance一定要改写。

 这一部分有以下几步:创建窗口,显示窗口,更新窗口。

 3.1 CFrameWnd::Create产生主窗口,并先注册窗口类

 CMyWinApp::InitInstance一开始new了一个CMyFrameWnd对象,从而引发相应的构造函数,其中主要是调用了CFrameWnd的成员函数Create(),来获得一个CMyFrameWnd对象,用作主窗口。

 Create函数有八个参数,分别代表WNDCLASS窗口类,窗口标题,窗口风格,区域,父窗口,菜单等等。

 Create函数执行时,先看需不需要装载菜单,然后调用CreateEx()函数,此处实际调用的是父类CWnd::CreateEx()

 在Wincore.cpp中,有CWnd::CreateEx()的定义,主要是:
 1. 设置一些属性值;
 2. PreCreateWindow();该函数是虚函数,主要完成注册窗口类操作,其中用到了一些
   宏展开,会根据窗口的具体类型(如MDI窗口,SDI窗口等)注册具体的窗口类(如
   AfxMDIFrame42d,AfxFrameOrView42等),当然,Application Framework会把窗口类
   名自动转换成Afx:x:y:z:w的格式,成为独一无二的类名称,x,y,z,w分别表示窗口风
   格的hex值,窗口鼠标的hex值,背景颜色的hex值和窗口图标的hex值。
 3. 其他操作(具体情况还不知道,得等到看到后面时才会明白)

 
 3.2 窗口的显示与更新

 BOOL CMyWinApp::InitInstance(){
     m_pMainWnd = new CMyFrameWnd();
     m_pMainWnd -> ShowWindow(m_nCmdShow);
     m_pMainWnd -> UpdateWindow();
     return TRUE;
 }

 4. CWinApp::Run—-程序生命的活水源头

 
 pApp->Run();因为Run是虚拟函数,而且用户通常不去重载该函数,所以调用的是CWinApp::Run(),该函数中,主要是调用CWinThread::Run();

 CWinThread::Run跟前面的SDK的消息循环不同在于,它除了处理消息循环外,还要处理Idle time:
     for (;;;){//该循环只有在碰到WM_QUIT消息时才退出
         处理空闲时间;
         处理消息循环;
     }

 消息循环部分是由PumpMessage()来完成,不停地通过TranslateMessage(),和DispatchMessage()来转换,分发消息。

 DispatchMessage后,消息被发送到相应的窗口函数,当然,窗口函数已经由MFC提供了—-在我们注册窗口类时,已经指定了窗口函数:
 wndcls.lpfnWndProc = DefWindowProc;
     
 消息映射机制

 消息映射时,MFC采用默认的规则,即看到一条ON_WM_CLOSE的宏,就会相应的将WM_CLOSE与OnClose处理函数关联起来。故只要声明了消息处理函数:
    class CMyFrameWnd :…{
    public:
      afx_msg void OnPaint();//声明消息处理函数
      ……
    }

 并定义了对应的宏,即在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间加入ON_WM_PAINT即可。

 MFC来龙去脉总整理

 程序的诞生
 1.在进入AfxWinMain函数前,产生全局的application object,内存获得配置,设立初
   值;
 2.AfxWinMain中执行AfxWinInit,后者又调用AfxInitThread,把消息队列
   尽量加大到96

 3.AfxWinMain执行InitApplication。这是CWinApp的虚函数,通常我们不改写它;
 4.AfxWinMain执行InitInstance。也是CwinApp的虚函数,但我们必须改写它;
 5.CMyWinApp::InitInstance ‘new’了一个CMyFrameWnd对象;
 6.CMyFrameWnd构造函数调用Create,产生主窗口。我们在Create参数中指定的窗口类
   是NULL,于是MFC根据窗口种类,自行为我们注册一个名为“AfxFrameOrView42d”的
   窗口类;
 7.回到InitInstance中继续执行ShowWindow,显示窗口;
 8.执行UpadteWindow,于是发出WM_PAINT;
 9.回到AfxWinMain,执行Run,进入消息循环。

 程序的运行
 1.程序获得WM_PAINT消息(由CWinApp::Run中的::GetMessage循环获得;
 2.WM_PAINT经由::DispatchMessage送到窗口函数CWnd::DefWindowProc中;
 3.CWnd::DefWindowProc将消息传递到消息映射表格;
 4.传递过程中发现有相符项目,于是调用对应的函数。此函数是利用
   BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的宏设立起来的;
 5.标准消息的处理程序亦有标准命名,如WM_PAINT必然由OnPaint处理。

 程序的死亡
 1.用户单击file/close,于是发出WM_CLOSE;
 2.CMyFrameWnd通常不设置处理该消息的程序,于是交给默认的处理程序;
 3.默认函数对WM_CLOSE的处理方式是调用::DestroyWindow,并因而发出WM_DESTROY;
 4.默认的WM_DESTROY处理方式是调用::PostQuitMessage,因而发出WM_QUIT;
 5.CWinApp::Run收到WM_QUIT后悔结束内部的消息循汉,。然后调用ExitInstance,这
   是CWinApp的一个虚拟函数,用户可以改写;
 6.最后回到AfxWinMain,执行AfxWinTerm,结束程序。

 Callback函数

 Hello中使用了LineDDA(),这是一个Windows API函数:
     void WINAPI LineDDA(int,int,int,int,LINEDDAPROC,LPARAM);
 前面是个参数指定了起始点的横纵座标,该函数将根据Bresenham算法计算对应直线所通过的每个屏幕像素座标,每计算出一个,就调用第五个参数所指定的callback函数,这个callback函数的类型必须是:
     typedef void (CALLBACK* LINEDDAPROC) (int,int,LPARAM);
 此处,LineDDA不属于任何一个MFC类,所以调用时必须加上全局操作符(::):
     void CMyFrameWnd::OnPaint(){
          CPaintDC dc(this);
          CRect rect;
          GetClientRect(rect);
          dc.SetTextAlign(TA_BOTTOM|TA_CENTER);
          ::LineDDA(rect.right/2,0,rect.right/2,rect.bottom/2,
              (LINEDDAPROC) LineDDACallback,(LPARAM) (LPVOID) &dc);
     };

 其中的LineDDACallback是我们准备的callback函数,必须在类中有声明(按照LineDDA所要求的callback函数类型),并要把它声明为“static”,以把C++编译器加诸于普通类成员函数的一个隐藏参数this去掉。

 凡是由你设计而由Windows系统调用的函数,称为callback函数,这些函数都有一定类型,以配合Windows的调用。
 一方面,某些Windows API函数会以callback函数作为其参数之一,如LineDDA函数;
 另一方面,callback函数带有所要求的类型,以便于上述API函数调用。

 但由于C++给类中的成员函数(包括普通的callback函数)都加了一个隐藏的this参数,而Windows callback函数并没有要求有该参数,所以,我们在把某个函数用作callback函数时,必须告诉编译器不要加入this参数,可以:

 1.用全局函数作callback函数;(C语言的做法)
 2.用static的成员函数作callback函数。(更符合OO)

 空闲时间的处理:OnIdle

 CwinThread在处理消息循环前,还处理了空闲时间:发现当前状态处于空闲(系统还是程序),就调用OnIdle()函数。该函数做一些MFC本身的维护工作。

 如果想在自己的MFC程序中处理空闲时间,可以改写CWinApp派生类的OnIdle函数。

 Dialog和Control

 当我们点“file/about”菜单项时,命令消息WM_COMMAND(IDM_ABOUT)被送到OnAbout函数。我们现在OnAbout函数中产生一个CDialog对象,about:
     CDialog about (“AboutBox”,this);//前者指定资源模板(在RC文件中)
                                           //后者指定about的主人

 然后运行对话框:  
    about.DoModal();

 通用对话框
 Open对话框,获得文件路径:

     char szFilters[]="Text files(*.txt)|*.txt|All files(*.*)|*.*||"
    
     CFileDialog opendlg (
          TRUE,    //说明对话框是“Open”而非“Save as”
          "txt",   //默认扩展名
          "*.txt",  //默认文件名
          OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, //指定文件的属性
          szFilters,//可以选择的文件类型
          this)    //父窗口

     if (opendlg.DoModal() == IDOK){
          filename = opendlg.GetPathName();
     }

抱歉!评论已关闭.