您的位置:首页 > 其它

六、MFC 程序的生死因果 (学习笔记)

2013-07-03 20:51 489 查看

MFC程序的生死因果

项目HELLO

理想如果不向实际做点妥协,理想就会归于尘土。
中华民国还得十次革命才得建立,对象导向怎能把一切传统都抛开。

以传统的C/SDK 撰写Windows 程序,最大的好处是可以清楚看见整个程序的来龙去脉和消息动向,然而这些重要的动线在MFC 应用程序中却隐晦不明,因为它们被Application Framework包起来了。这一章主要目的除了解释MFC 应用程序的长像,也要从MFC 源代码中检验出一个Windows 程序原本该有的程序进入点(WinMain)、视窗类别注册(RegisterClass)、窗口产生(CreateWindow )、消息循环(Message
Loop )、窗口函数(Window Procedure)等等动作,抽丝剥茧彻底了解一个MFC 程序的诞生与结束,以及生命过程。

为什么要安排这一章?了解MFC 内部构造是必要的吗?看电视需要知道映射管的原理吗?开汽车需要知道传动轴与变速箱的原理吗?学习MFC 不就是要一举超越烦琐的Windows API?啊,厂商(不管是哪一家)广告给我们的印象就是,藉由可视化的工具我们可以一步登天,基本上这个论点正确,只是有个但是:你得学会操控Application Framework 。

想象你拥有一部保时捷,风驰电挚风光得很,但是引擎盖打开来全傻了眼。如果你懂汽车内部运作原理,那么至少开车时「脚不要老是含着离合器,以免来令片磨损」这个道理背后的原理你就懂了,「踩煞车时绝不可以同时踩离合器,以免失去引擎煞车力」这个道理背后的原理你也懂了,甚至你的保时捷要保养维修时或也可以不假外力自己来。

不要把自己想象成这场游戏中的后座车主,事实上作为这本技术书籍的读者的你,应该是车厂师傅。









我希望你了解,本书之所以在各个主题中不厌其烦地挖MFC内部动作,解释骨干程序的每一条指令,每一个环节,是为了让你踏实地接受MFC,进而有能力役使MFC。你以为这是一条远路?呵呵,似远实近!

不二法门:熟记MFC 类别的阶层架构

MFC 在1.0版时期的诉求是「一组将SDK API 包装得更好用的类别库」,从2.0版开始更进一步诉求是一个「Application Framework」,拥有重要的Document-View架构;随后又在更新版本上增加了OLE 架构、DAO 架构... 。为了让你有一个最轻松的起点,我把第一个程序简化到最小程度,舍弃Document-View  架构,使你能够尽快掌握C++/MFC 程序的面貌。这个程序并不以AppWizard 制作出来,也不以ClassWizard
管理维护,而是纯手工打造。毕竟Wizards 做出来的程序代码有一大堆批注,某些批注对Wizards 有特殊意义,不能随便删除,却可能会混淆初学者的视听焦点;而且Wizards 所产生的程序骨干已具备Document-View  架构,又有许多奇奇怪怪的宏,初学者暂避为妙。我们目前最想知道的是一个最阳春的MFC 程序以什么面貌呈现,以及它如何开始运作,如何结束生命。

SDK 程序设计的第一要务是了解最重要的数个API  函数的意义和用法,像是RegisterClass、CreateWindow 、GetMessage 、DispatchMessage,以及消息的获得与分配。MFC 程序设计的第一要务则是熟记MFC 的类别阶层架构,并清楚知晓其中几个一定会用到的类别。本书最后面有一张MFC 4.2 架构图,叠床架屋,令人畏惧,我将挑出单单两个类别,组合成一个"Hello MFC"  程序。这两个类别在MFC的地位如图6-1所示。



需要什么函数库?

开始写码之前,我们得先了解程序代码以外的外围环境。第一个必须知道的是,MFC 程序需要什么函数库?SDK 程序联结时期所需的函数库已在第一章显示,MFC 程序一样需要它们:





此外,应用程序还需要联结一个所谓的MFC 函数库,或称为AFX 函数库,它也就是MFC这个application framework  的本体。你可以静态联结之,也可以动态联结之,AppWizard给你选择权。本例使用动态联结方式,所以需要一个对应的MFC import 函数库:



我们如何在联结器(link.exe )中设定选项,把这些函数库都联结起来?稍后在HELLO.MAK中可以一窥全貌。

如果在Visual C++  整合环境中工作,这些设定不劳你自己动手,整合环境会根据我们圈选的项目自动做出一个合适的makefile。这些makefile 的内容看起来非常诘屈聱牙,事实上我们也不必太在意它,因为那是整合环境的工作。这一章我不打算依赖任何开发工具,一切自己来,你会在稍后看到一个简洁清爽的makefile。

需要什么头文件?

SDK 程序只要包含WINDOWS.H 就好,所有API  的函数声明、消息定义、常数定义、宏定义、都在WINDOWS.H 档中。除非程序另调用了操作系统提供的新模块(如CommDlg、ToolHelp 、DDEML...),才需要再各别包含对应的.H 档。



MFC 程序不这么单纯,下面是它常常需要面对的另外一些.H 档:

■STDAFX.H -  这个文件用来做为Precompiled header file(请看稍后的方块说明),其内只是包含其它的MFC 头文件。应用程序通常会准备自己的STDAFX.H ,例如本章的Hello 程序就在STDAFX.H 中包含AFXWIN.H。

■AFXWIN.H -  每一个Windows MFC 程序都必须包含它,因为它以及它所包含的文件声明了所有的MFC 类别。此档内含AFX.H,后者又包含AFXVER_.H,后者又包含AFXV_W32.H,后者又包含WINDOWS.H(啊呼,终于现身)。

■AFXEXT.H -  凡使用工具栏、状态列之程序必须包含这个文件。

■AFXDLGS.H - 凡使用通用型对话框(Common Dialog)之MFC 程序需包含此档,其内部包含COMMDLG.H。

■AFXCMN.H - 凡使用Windows 95 新增之通用型控制组件(Common Control)之MFC 程序需包含此文件。

■AFXCOLL.H - 凡使用Collections Classes (用以处理数据结构如数组、串行)之程序必须包含此文件。

■AFXDLLX.H - 凡MFC extension DLLs 均需包含此档。

■AFXRES.H - MFC 程序的RC文件必须包含此档。MFC 对于标准资源(例如File、Edit  等菜单项目)的ID 都有默认值,定义于此文件中,例如:

 

// File commands
#define ID_FILE_NEW         0xE100
#define ID_FILE_OPEN        0xE101
#define ID_FILE_CLOSE       0xE102
#define ID_FILE_SAVE        0xE103
#define ID_FILE_SAVE_AS     0xE104
...
// Edit commands
#define ID_EDIT_COPY        0xE122
#define ID_EDIT_CUT         0xE123
...


这些菜单项目都有预设的说明文字(将出现在状态列中),但说明文字并不会事先定义于此文件,AppWizard 为我们制作骨干程序时才把说明文字加到应用程序的RC文件中。第4章的骨干程序Scribble step0  的RC 档中就有这样的字符串表格:

STRINGTABLE DISCARDABLE
BEGIN
ID_FILE_NEW      "Create a new document"
ID_FILE_OPEN     "Open an existing document"
ID_FILE_CLOSE    "Close the active document"
ID_FILE_SAVE     "Save the active document"
ID_FILE_SAVE_AS  "Save the active document with a new name"
...
ID_EDIT_COPY "Copy the selection and puts it on the Clipboard"
ID_EDIT_CUT  "Cut the selection and puts it on the Clipboard"
...
END


所有MFC 头文件均置于\MSVC\MFC\INCLUDE  中。这些文件连同Windows SDK 的包含档WINDOWS.H、COMMDLG.H、TOOLHELP.H、DDEML.H... 每每在编译过程中耗费大量的时间,因此你绝对有必要设定Precompiled header 。



简化的MFC 程序架构-以Hello MFC 为例

现在我们正式进入MFC 程序设计。由于Document/View架构复杂,不适合初学者,所以我先把它略去。这里所提的程序观念是一般的MFC Application Framework 的子集合。本章程序名为Hello,执行时会在窗口中从天而降"Hello, MFC"   字样。Hello 是一个非常简单而具代表性的程序,它的代表性在于:

■   每一个MFC 程序都想从MFC 中衍生出适当的类别来用(不然又何必以MFC 写程序呢),其中两个不可或缺的类别CWinApp 和CFrameWnd 在Hello程序中会表现出来,它们的意义如图6-2。

■   MFC 类别中某些函数一定得被应用程序改写(例如CWinApp :: InitInstance),这在Hello 程序中也看得到。

■   菜单和对话框,Hello 也都具备。

 

图6-3 是Hello 源文件的组成。第一次接触MFC 程序,我们常常因为不熟悉MFC 的类别分类、类别命名规则,以至于不能在脑中形成具体印象,于是细部讨论时各种信息及说明彷如过眼云烟。相信我,你必须多看几次,并且用心熟记MFC 命名规则。

图6-3  之后是Hello 程序的源代码。由于MFC 已经把Windows API 都包装起来了,源代码再也不能够「说明一切」。你会发现MFC 程序很有点见林不见树的味道:

■看不到WinMain,因此不知程序从哪里开始执行。

■看不到RegisterClass 和CreateWindow ,那么窗口是如何做出来的呢?

■看不到Message Loop (GetMessage /DispatchMessage ),那么程序如何推动?

■看不到Window Procedure,那么窗口如何运作?

我的目的就在铲除这些困惑。

Hello  程序源代码

■   HELLO.MAK - makefile

■   RESOURCE.H -   所有资源ID 都在这里定义。本例只定义一个IDM_ABOUT。

■   JJHOUR.ICO -  图标文件,用于主窗口和对话框。

■   HELLO.RC - 资源描述档。本例有一份菜单、一个图标、和一个对话框。

■   STDAFX.H -   包含AFXWIN.H。

■   STDAFX.CPP -  包含STDAFX.H ,为的是制造出Precompiled header 。

■   HELLO.H -   声明CMyWinApp 和CMyFrameWn d。

■   HELLO.CPP -  定义CMyWinApp 和CMyFrameWn d。

注意:没有模块定义文件.DEF?是的,如果你不指定模块定义文件,联结器就使用默认值。





MFC 程序的来龙去脉(causal relations)

让我们从第1章的C/SDK 观念出发,看看MFC 程序如何运作。

第一件事情就是找出MFC 程序的进入点。MFC 程序也是Windows 程序,所以它应该也有一个WinMain,但是我们在Hello 程序看不到它的踪影。是的,但先别急,在程序进入点之前,更有一个(而且仅有一个)全域对象(本例名为theApp ),这是所谓的application object,当操作系统将程序加载并激活,这个全域对象获得配置,其构造式会先执行,比WinMain  更早。所以以时间顺序来说,我们先看看这个application object。

我只借用两个类别:CWinApp 和 CFrameWnd

你已经看过了图6-2,作为一个最最粗浅的MFC 程序,Hello 是如此单纯,只有一个视窗。回想第一章Generic 程序的写法,其主体在于WinMain  和WndProc,而这两个部份其实都有相当程度的不变性。好极了,MFC 就把有着相当固定行为之WinMain  内部动作包装在CWinApp 中,把有着相当固定行为之WndProc  内部动作包装在CFrameWnd 中。也就是说:

■   CWinApp 代表程序本体

■   CFrameWnd 代表一个主框窗口(Frame Window)

但虽然我说,WinMain内部动作和WndProc内部动作都有着相当程度的固定行为,它们毕竟需要面对不同应用程序而有某种变化。所以,你必须以这两个类别为基础,衍生自己的类别,并改写其中一部份成员函数。

class CMyWinApp : public CWinApp
{
...
};
class CMyFrameWnd : public CFrameWnd
{
...
};


本章对衍生类别的命名规则是:在基础类别名称的前面加上" My" 。这种规则真正上战场时不见得适用,大型程序可能会自同一个基础类别衍生出许多自己的类别。不过以教学目的而言,这种命名方式使我们从字面就知道类别之间的从属关系,颇为理想(根据我的经验,初学者会被类别的命名搞得头昏脑胀)。

CWinApp-取代 WinMain的地位

CWinApp的衍生对象被称为application object,可以想见,CWinApp本身就代表一个程式本体。一个程序的本体是什么?回想第1章的SDK 程序,与程序本身有关而不与视窗有关的资料或动作有些什么?系统传进来的四个WinMain参数算不算?InitApplication 和InitInstance  算不算?消息循环算不算?都算,是的,以下是MFC 4.x的CWinApp声明(节录自AFXWIN.H):

class CWinApp : public CWinThread
{
// Attributes
// Startup args (do not change)
HINSTANCE m_hInstance;
HINSTANCE m_hPrevInstance;
LPTSTR m_lpCmdLine;
int m_nCmdShow;
// Running args (can be changed in InitInstance)
LPCTSTR m_pszAppName;  // human readable name
LPCTSTR m_pszRegistryKey;   // used for registry entries
public:  // set in constructor to override default
LPCTSTR m_pszExeName;       // executable name (no spaces)
LPCTSTR m_pszHelpFilePath;  // default based on module path
LPCTSTR m_pszProfileName;   // default based on app name
public:
// hooks for your initialization code
virtual BOOL InitApplication();
// overrides for implementation
virtual BOOL InitInstance();
virtual int ExitInstance();
virtual int Run();
virtual BOOL OnIdle(LONG lCount);
...
};

几乎可以说CWinApp 用来取代WinMain  在SDK 程序中的地位。这并不是说MFC 程序没有WinMain(稍后我会解释),而是说传统上SDK 程序的WinMain  所完成的工作现在由CWinApp 的三个函数完成:

virtual BOOL InitApplication();
virtual BOOL InitInstance();
virtual int Run();

WinMain  只是扮演役使它们的角色。

会不会觉得CWinApp 的成员变量中少了点什么东西?是不是应该有个成员变量记录主窗口的handle (或是主窗口对应之C++ 对象)?的确,在MFC 2.5 中的确有m_pMainWnd这么个成员变量(以下节录自MFC 2.5 的AFXWIN.H):

class CWinApp : public CCmdTarget
{
// Attributes
// Startup args (do not change)
HINSTANCE m_hInstance;
HINSTANCE m_hPrevInstance;
LPSTR m_lpCmdLine;
int m_nCmdShow;
// Running args (can be changed in InitInstance)
CWnd* m_pMainWnd;           // main window (optional)
CWnd* m_pActiveWnd;         // active main window (may not be m_pMainWnd)
const char* m_pszAppName;   // human readable name
public:  // set in constructor to override default
const char* m_pszExeName;       // executable name (no spaces)
const char* m_pszHelpFilePath;  // default based on module path
const char* m_pszProfileName;   // default based on app name
public:
// hooks for your initialization code
virtual BOOL InitApplication();
virtual BOOL InitInstance();
// running and idle processing
virtual int Run();
virtual BOOL OnIdle(LONG lCount);
// exiting
virtual int ExitInstance();
...
};


但从MFC 4.x  开始,m_pMainWnd  已经被移往CWinThread 中了(它是CWinApp 的父类别)。以下内容节录自MFC 4.x  的AFXWIN.H:

class CWinThread : public CCmdTarget
{
// Attributes
CWnd* m_pMainWnd;     // main window (usually same AfxGetApp()->m_pMainWnd)
CWnd* m_pActiveWnd;   // active main window (may not be m_pMainWnd)
// only valid while running
HANDLE m_hThread;       // this thread's HANDLE
DWORD m_nThreadID;      // this thread's ID
int GetThreadPriority();
BOOL SetThreadPriority(int nPriority);
// Operations
DWORD SuspendThread();
DWORD ResumeThread();
// Overridables
// thread initialization
virtual BOOL InitInstance();
// running and idle processing
virtual int Run();
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL PumpMessage();     // low level message pump
virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing
public:
// valid after construction
AFX_THREADPROC m_pfnThreadProc;
...
};


熟悉Win32 的朋友,看到CWinThread 类别之中的SuspendThread 和ResumeThread 成员函数,可能会发出会心微笑。

CFrame Wnd-取代 WndProc的地位

CFrameWnd 主要用来掌握一个窗口,几乎你可以说它是用来取代SDK 程序中的窗口函式的地位。传统的SDK 窗口函数写法是:

long FAR PASCAL WndProc(HWND hWnd, UNIT msg, WORD wParam, LONG lParam)
{
switch(msg) {
case WM_COMMAND :
switch(wParam) {
case IDM_ABOUT :
OnAbout(hWnd, wParam, lParam);
break;
}
break;
case WM_PAINT :
OnPaint(hWnd, wParam, lParam);
break;
default :
DefWindowProc(hWnd, msg, wParam, lParam);
}
}


MFC 程序有新的作法,我们在Hello 程序中也为CMyFrameWnd  准备了两个消息处理例程,声明如下:

class CMyFrameWnd : public CFrameWnd
{
public:
CMyFrameWnd();
afx_msg void OnPaint();
afx_msg void OnAbout();
DECLARE_MESSAGE_MAP()
};


OnPaint 处理什么消息?OnAbout  又是处理什么消息?我想你很容易猜到,前者处理WM_PAINT ,后者处理WM_COMMAND 的IDM _ABOUT。这看起来十分俐落,但让人搞不懂来龙去脉。程序中是不是应该有「把消息和处理函数关联在一起」的设定动作?是的,这些设定在HELLO.CPP 才看得到。但让我先着一鞭:DECLARE_MESSAGE_MAP宏与此有关。

这种写法非常奇特,原因是MFC 内建了一个所谓的Message Map 机制,会把消息自动送到「与消息对映之特定函数」去;消息与处理函数之间的对映关系由程序员指定。DECLARE_MESSAGE_MAP  另搭配其它宏,就可以很便利地将消息与其处理函数关联在一起:

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)

ON_WM_PAINT()

ON_COMMAND(IDM_ABOUT, OnAbout)

END_MESSAGE_MAP()

稍后我就来探讨这些神秘的宏。

引爆器-Application object

我们已经看过HELLO.H 声明的两个类别,现在把目光转到HELLO.CPP 身上。这个档案将两个类别实作出来,并产生一个所谓的application object。故事就从这里展开。下面这张图包括右半部的Hello 源代码与左半部的MFC 源代码。从这一节以降,我将以此图解释MFC 程序的激活、运行、与结束。不同小节的图将标示出当时的程序进行状况。



上图的theApp就是Hello 程序的application object,每一个MFC 应用程序都有一个,而且也只有这么一个。当你执行Hello,这个全域对象产生,于是构造式执行起来。我们并没有定义CMyWinApp 构造式;至于其父类别CWinApp 的构造式内容摘要如下(摘录自APPCORE.CPP):

CWinApp::CWinApp(LPCTSTR lpszAppName)
{
m_pszAppName = lpszAppName;
// initialize CWinThread state
AFX_MODULE_THREAD_STATE* pThreadState = AfxGetModuleThreadState();
pThreadState->m_pCurrentWinThread = this;
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_pCurrentWinApp = this;
// in non-running state until WinMain
m_hInstance = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
...
}


CWinApp 之中的成员变量将因为theApp  这个全域对象的诞生而获得配置与初值。如果程序中没有theApp  存在,编译联结还是可以顺利通过,但执行时会出现系统错误消息:

隐晦不明的 WinMain



theApp  配置完成后,WinMain  登场。我们并未撰写WinMain  程序代码,这是MFC 早已准备好并由联结器直接加到应用程序代码中的,其源代码列于图6-4。_tWinMain  函数的-t是为了支持Unicode 而准备的一个宏。

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




// in WINMAIN.CPP

#0001  /////////////////////////////////////////////////////////////////

#0002  // Standard WinMain implementation

#0003  //  Can be replaced as long as 'AfxWinInit' is called first

#0004

#0005  int AFXAPI AfxWinMain  (HINSTANCE hInstance, HINSTANCE hPrevInstance,

#0006          LPTSTR lpCmdLine, int nCmdShow)

#0007  {

#0008      ASSERT(hPrevInstance == NULL);

#0009

#0010      int nReturnCode = -1;

#0011      CWinApp* pApp = AfxGetApp();

#0012

#0013      // AFX internal initialization

#0014      if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))

#0015              goto InitFailure;

#0016

#0017      // App global initializations (rare)

#0018      ASSERT_VALID(pApp);

#0019      if (!pApp->InitApplication())

#0020              goto InitFailure;

#0021      ASSERT_VALID(pApp);

#0022

#0023      // Perform specific initializations

#0024      if (!pApp->InitInstance())

#0025      {

#0026          if (pApp->m_pMainWnd != NULL)

#0027          {

#0028              TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");

#0029              pApp->m_pMainWnd->DestroyWindow();

#0030          }

#0031          nReturnCode = pApp->ExitInstance();

#0032          goto InitFailure;

#0033      }

#0034      ASSERT_VALID(pApp);

#0035

#0036      nReturnCode = pApp->Run();

#0037      ASSERT_VALID(pApp);

#0038

#0039  InitFailure:

#0040

#0041      AfxWinTerm();

#0042      return nReturnCode;

#0043  }

Windows 程序进入点。源代码可从MFC 的WINMAIN.CPP 中获得。

稍加整理去芜存菁,就可以看到这个「程序进入点」主要做些什么事:

int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                 LPTSTR lpCmdLine, int nCmdShow)
{
int nReturnCode = -1;
CWinApp* pApp = AfxGetApp();
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();
AfxWinTerm();
return nReturnCode;
}


其中,AfxGetApp 是一个全域函数,定义于AFXWIN1.INL  中:

_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
{ return afxCurrentWinApp; }

而afxCurrentWinApp  又定义于AFXWIN.H 中:

#define afxCurrentWinApp  AfxGetModuleState()->m_pCurrentWinApp

再根据稍早所述CWinApp :: CWinApp 中的动作,我们于是知道,AfxGetApp 其实就是取得CMyWinApp 对象指针。所以,AfxWinMain 中这样的动作:

CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();


其实就相当于调用:

CMyWinApp::InitApplication();

CMyWinApp::InitInstance();

CMyWinApp::Run();

因而导至调用:

CWinApp::InitApplication();  //因为CMyWinApp 并没有改写InitApplication

CMyWinApp::InitInstance();   //因为CMyWinApp 改写了InitInstance

CWinApp::Run();               //因为CMyWinApp 并没有改写Run

根据第1章SDK 程序设计的经验推测,InitApplication 应该是注册窗口类别的场所?InitInstance  应该是产生窗口并显示窗口的场所?Run  应该是攫取消息并分派消息的场所?有对有错!以下数节我将实际带你看看MFC 的源代码,如此一来就可以了解隐藏在MFC 背后的玄妙了。我的终极目标并不在MFC 源代码(虽然那的确是学习设计一个application framework 的好教材),我只是想拿把刀子把MFC 看似朦胧的内部运作来个大解剖,挑出其经脉;有这种扎实的根基,使用MFC 才能知其然并知其所以然。下面小节分别讨论AfxWinMain
的四个主要动作以及引发的行为。

AfxWinInit-AFX 内部初始化动作



我想你已经清楚看到了,AfxWinInit 是继CWinApp 构造式之后的第一个动作。以下是它的动作摘要(节录自APPINIT.CPP):

BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
// set resource handles
AFX_MODULE_STATE* pState = AfxGetModuleState();
pState->m_hCurrentInstanceHandle = hInstance;
pState->m_hCurrentResourceHandle = hInstance;
// fill in the initial state for the application
CWinApp* pApp = AfxGetApp();
if (pApp != NULL)
{
// Windows specific initialization (not done if no CWinApp)
pApp->m_hInstance = hInstance;
pApp->m_hPrevInstance = hPrevInstance;
pApp->m_lpCmdLine = lpCmdLine;
pApp->m_nCmdShow = nCmdShow;
pApp->SetCurrentHandles();
}
// initialize thread specific data (for main thread)
if (!afxContextIsDLL)
AfxInitThread();
return TRUE;
}


其中调用的AfxInitThread  函数的动作摘要如下(节录自THRDCORE.CPP):

void AFXAPI AfxInitThread()
{
if (!afxContextIsDLL)
{
// attempt to make the message queue bigger
for (int cMsg = 96; !SetMessageQueue(cMsg) && (cMsg -= 8); )
;
// set message filter proc
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
ASSERT(pThreadState->m_hHookOldMsgFilter == NULL);
pThreadState->m_hHookOldMsgFilter =  ::SetWindowsHookEx (WH_MSGFILTER,
_AfxMsgFilterHook, NULL, ::GetCurrentThreadId());
// intialize CTL3D for this thread
_AFX_CTL3D_STATE* pCtl3dState = _afxCtl3dState;
if (pCtl3dState->m_pfnAutoSubclass != NULL)
(*pCtl3dState->m_pfnAutoSubclass)(AfxGetInstanceHandle());
// allocate thread local _AFX_CTL3D_THREAD just for automatic termination
_AFX_CTL3D_THREAD* pTemp = _afxCtl3dThread;
}
}


如果你曾经看过本书前身Visual C++  对象导向MFC 程序设计,我想你可能对这句话印象深刻:「WinMain  一开始即调用AfxWinInit,注册四个窗口类别」。这是一个已成昨日黄花的事实。MFC 的确会为我们注册四个窗口类别,但不再是在AfxWinInit 中完成。稍后我会把注册动作挖出来,那将是窗口诞生前一刻的行为。

CWinApp::InitApplication



AfxWinInit 之后的动作是pApp-> InitApplication 。稍早我说过了,pApp 指向CMyWinApp对象(也就是本例的theApp ),所以,当程序调用:

pApp->InitApplication();

相当于调用:

CMyWinApp::InitApplication();

但是你要知道,CMyWinApp 继承自CWinApp ,而InitApplication 又是CWinApp 的一个虚拟函数;我们并没有改写它(大部份情况下不需改写它),所以上述动作相当于调用:

CWinApp::InitApplication();

此函数之源代码出现在APPCORE.CPP  中:

BOOL CWinApp::InitApplication()
{
if (CDocManager::pStaticDocManager != NULL)
{
if (m_pDocManager == NULL)
m_pDocManager = CDocManager::pStaticDocManager;
CDocManager::pStaticDocManager = NULL;
}
if (m_pDocManager != NULL)
m_pDocManager->AddDocTemplate(NULL);
else
CDocManager::bStaticInit = FALSE;
return TRUE;
}


这些动作都是MFC 为了内部管理而做的。

关于Document Template 和CDocManager ,第7章和第8章另有说明。

CMyWinApp::InitInstance



继InitApplication 之后,AfxWinMain 调用pApp-> InitInstance 。稍早我说过了,pApp 指向CMyWinApp 对象(也就是本例的theApp ),所以,当程序调用:

pApp->InitInstance();

相当于调用

CMyWinApp::InitInstance();

但是你要知道,CMyWinApp 继承自CWinApp ,而InitInstance  又是CWinApp 的一个虚拟函数。由于我们改写了它,所以上述动作的的确确就是调用我们自己(CMyWinApp)的这个InitInstance  函数。我们将在该处展开我们的主窗口生命。



CFrameWnd::Create 产生主窗口(并先注册窗口类别)



CMyWinApp :: InitInstance  一开始new  了一个CMyFrameWnd  对象,准备用作主框窗口的C++ 对象。new  会引发构造式:

CMyFrameWnd::CMyFrameWnd
{
Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL,
"MainMenu");
}


其中Create  是CFrameWnd 的成员函数,它将产生一个窗口。但,使用哪一个窗口类别呢?



根据CFrameWnd:: Create  的规格:

BOOL Create( LPCTSTR lpszClassName,

               LPCTSTR lpszWindowName,

               DWORD dwStyle = WS_OVERLAPPEDWINDOW,

               const RECT& rect = rectDefault,

               CWnd* pParentWnd = NULL,

               LPCTSTR lpszMenuName = NULL,

               DWORD dwExStyle = 0,

               CCreateContext* pContext = NULL );

八个参数中的后六个参数都有默认值,只有前两个参数必须指定。

第一个参数lpszClassName  指定WNDCLASS 窗口类别,我们放置NULL  究竟代表什么意思?意思是要以MFC 内建的窗口类别产生一个标准的外框窗口。但,此时此刻Hello
程序中根本不存在任何窗口类别呀!噢,Create函数在产生窗口之前会引发窗口类别的注册动作,稍后再解释。

第二个参数 lpszWindowName  指定窗口标题,本例指定"Hello MFC" 。第三个参数dwStyle 指定窗口风格,预设是WS_OVERLAPPEDWINDOW,也正是最常用的一种,它被定义为(在WINDOWS.H 之中):

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION |

                                  WS_SYSMENU | WS_THICKFRAME |

                                  WS_MINIMIZEBOX  |  WS_MAXIMIZEBOX)

因此如果你不想要窗口右上角的极大极小钮,就得这么做:

Create(NULL,

        "Hello  MFC",

        WS_OVERLAPPED  | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME

        rectDefault,

        NULL,

        "MainMenu");

如果你希望窗口有垂直滚动条,就得在第三个参数上再加增WS_VSCROLL 风格。除了上述标准的窗口风格,另有所谓的扩充风格,可以在Create  的第七个参数dwExStyle 指定之。扩充风格唯有以:: CreateWindowEx(而非:: CreateWindow )函数才能完成。事实上稍后你就会发现,CFrameWnd::
Create  最终调用的正是:: CreateWindowEx 。Windows 3.1 提供五种窗口扩充风格:

WS_EX_DLGMODALFRAME

WS_EX_NOPARENTNOTIFY

WS_EX_TOPMOST

WS_EX_ACCEPTFILES

WS_EX_TRANSPARENT

Windows 95 有更多选择,包括WS_EX_WINDOWEDGE 和WS_EX_CLIENTEDGE,让窗口更具3D 立体感。Framework  已经自动为我们指定了这两个扩充风格。Create  的第四个参数rect   指定窗口的位置与大小。默认值rectDefault   是CFrameWnd的一个static  成员变量,告诉Windows 以预设方式指定窗口位置与大小,就好象在SDK 程序中以CW_USEDEFAULT 指定给CreateWindow
函数一样。如果你很有主见,希望窗口在特定位置有特定大小,可以这么做:

Create(NULL,

        "Hello MFC",

        WS_OVERLAPPEDWINDOW,

        CRect(40, 60, 240,  460),  // 起始位置 (40,60) ,寬 200,高 400)

        NULL,

        "MainMenu");

第五个参数pParentWnd 指定父窗口。对于一个top-level  窗口而言,此值应为NUL L ,表示没有父窗口(其实是有的,父窗口就是desktop 窗口)。

第六个参数lpszMenuName  指定菜单。本例使用一份在RC 中准备好的菜单MainMenu。

第八个参数pContext  是一个指向CCreateContext 结构的指针,framework利用它,在具备Document/View  架构的程序中初始化外框窗口(第8章的「CDocTemplate管理CDocument / CView / CFrameWnd」一节中将谈到此一主题)。本例不具备

Document/View  架构,所以不必指定pContext  参数,默认值为NULL。前面提过,CFrameWnd:: Create  在产生窗口之前,会先引发窗口类别的注册动作。让我再扮一次MFC 向导,带你寻幽访胜。你会看到MFC 为我们注册的窗口类别名称,及注册动作。

WINFRM.CPP

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,

                           LPCTSTR lpszWindowName,

                           DWORD dwStyle,

                           const RECT& rect,

                           CWnd* pParentWnd,

                           LPCTSTR lpszMenuName,

                           DWORD dwExStyle,

                           CCreateContext* pContext)

{

    HMENU hMenu = NULL;

    if (lpszMenuName != NULL)

    {

        // load in a menu that will get destroyed when window gets destroyed

        HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);

        hMenu = ::LoadMenu(hInst, lpszMenuName);

    }

    m_strTitle = lpszWindowName;    // save title for later

    CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,

          rect.left, rect.top,  rect.right - rect.left, rect.bottom - rect.top,

          pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext);


    return TRUE;

}

函数中调用CreateEx 。注意,CWnd  有成员函数CreateEx ,但其衍生类别CFrameWnd 并无,所以这里虽然调用的是CFrameWnd:: CreateEx ,其实乃是从父类别继承下来的CWnd::CreateEx 。

WINCORE.CPP

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,

                        LPCTSTR lpszWindowName, DWORD dwStyle,

                        int x, int y, int nWidth, int nHeight,

                        HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)

{

    // allow modification of several common create parameters

    CREATESTRUCT cs;

    cs.dwExStyle = dwExStyle;

    cs.lpszClass = lpszClassName;

    cs.lpszName = lpszWindowName;

    cs.style = dwStyle;

    cs.x = x;

    cs.y = y;

    cs.cx = nWidth;

    cs.cy = nHeight;

    cs.hwndParent = hWndParent;

    cs.hMenu = nIDorHMenu;

    cs.hInstance = AfxGetInstanceHandle();

    cs.lpCreateParams = lpParam;

    PreCreateWindow(cs);

    AfxHookWindowCreate(this);  //此动作将在第9章探讨。

    HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,

                    cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,

                    cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);


    ...

}

函数中调用的PreCreateWindow 是虚拟函数,CWnd  和CFrameWnd 之中都有定义。由于this  指针所指对象的缘故,这里应该调用的是CFrameWnd:: PreCreateWindow(还记得第2章我说过虚拟函数常见的那种行为模式吗?)

WINFRM.CPP

// CFrameWnd second phase creation

BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)

{

    if (cs.lpszClass == NULL)

    {

        AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);

        cs.lpszClass = _afxWndFrameOrView;  // 各个类中定义了不同的precreatewindow ! 使用不同的串口列别


    }

    ...

}

其中AfxDeferRegisterClass 是一个定义于AFXIMPL.H  中的宏。

AFXIMPL.H

#define  AfxDeferRegisterClass(fClass) \

  ((afxRegisteredClasses  & fClass) ? TRUE :  AfxEndDeferRegisterClass(fClass))

这个宏表示,如果变量afxRegisteredClasses  的值显示系统已经注册了fClass  这种视窗类别,MFC 就啥也不做;否则就调用AfxEndDeferRegisterClass(fClass),准备注册之。afxRegisteredClasses  定义于AFXWIN.H,是一个旗标变量,用来记录已经注册了哪些视窗类别:

// in AFXWIN.H

#define  afxRegisteredClasses   AfxGetModuleState()->m_fRegisteredClasses

WINCORE.CPP :

#0001  BOOL AFXAPI AfxEndDeferRegisterClass(short fClass)

#0002  {

#0003      BOOL bResult = FALSE;

#0004

#0005      // common initialization

#0006      WNDCLASS wndcls;

#0007      memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults

#0008      wndcls.lpfnWndProc = DefWindowProc;

#0009      wndcls.hInstance = AfxGetInstanceHandle();

#0010      wndcls.hCursor = afxData.hcurArrow;

#0011

#0012      AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

#0013      if (fClass &  AFX_WND_REG)

#0014      {

#0015          // Child windows - no brush, no icon, safest default class styles

#0016          wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;

#0017          wndcls.lpszClassName =  _afxWnd;

#0018          bResult =  AfxRegisterClass(&wndcls);

#0019          if (bResult)

#0020              pModuleState->m_fRegisteredClasses |= AFX_WND_REG;

#0021      }

#0022      else if (fClass &  AFX_WNDOLECONTROL_REG)

#0023      {

#0024          // OLE Control windows - use parent DC for speed

#0025          wndcls.style |= CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;

#0026          wndcls.lpszClassName = _afxWndOleControl;

#0027          bResult =  AfxRegisterClass(&wndcls);

#0028          if (bResult)

#0029              pModuleState->m_fRegisteredClasses |= AFX_WNDOLECONTROL_REG;

#0030      }

#0031      else if (fClass &  AFX_WNDCONTROLBAR_REG)

#0032      {

#0033          // Control bar windows

#0034          wndcls.style = 0;   // control bars don't handle double click

#0035          wndcls.lpszClassName = _afxWndControlBar;

#0036          wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);

#0037          bResult =  AfxRegisterClass(&wndcls);

#0038          if (bResult)

#0039              pModuleState->m_fRegisteredClasses |= AFX_WNDCONTROLBAR_REG;

#0040      }

#0041      else if (fClass &  AFX_WNDMDIFRAME_REG)

#0042      {

#0043          // MDI Frame window (also used for splitter window)

#0044          wndcls.style = CS_DBLCLKS;

#0045          wndcls.hbrBackground = NULL;

#0046          bResult = RegisterWithIcon(&wndcls,_afxWndMDIFrame,AFX_IDI_STD_MDIFRAME);

#0047          if (bResult)

#0048              pModuleState->m_fRegisteredClasses |= AFX_WNDMDIFRAME_REG;

#0049      }

#0050      else if (fClass &  AFX_WNDFRAMEORVIEW_REG)

#0051      {

#0052          // SDI Frame or MDI Child windows or views - normal colors

#0053          wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;

#0054          wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);

#0055          bResult = RegisterWithIcon(&wndcls, _afxWndFrameOrView,

                                                AFX_IDI_STD_FRAME);

#0056          if (bResult)

#0057              pModuleState->m_fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;

#0058      }

#0059      else if (fClass &  AFX_WNDCOMMCTLS_REG)

#0060      {

#0061          InitCommonControls();

#0062          bResult = TRUE;

#0063          pModuleState->m_fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;

#0064      }

#0065

#0066      return bResult;

#0067  }

出现在上述函数中的六个窗口类别卷标代码,分别定义于AFXIMPL.H  中:

#define  AFX_WND_REG               (0x0001)

#define  AFX_WNDCONTROLBAR_REG    (0x0002)

#define  AFX_WNDMDIFRAME_REG      (0x0004)

#define AFX_WNDFRAMEORVIEW_REG  (0x0008)

#define  AFX_WNDCOMMCTLS_REG      (0x0010)

#define AFX_WNDOLECONTROL_REG  (0x0020)

出现在上述函数中的五个窗口类别名称,分别定义于WINCORE.CPP 中:

const TCHAR _afxWnd[] = AFX_WND;

const TCHAR _afxWndControlBar[] = AFX_WNDCONTROLBAR;

const TCHAR _afxWndMDIFrame[] = AFX_WNDMDIFRAME;

const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW;

const TCHAR _afxWndOleControl[] = AFX_WNDOLECONTROL;

而等号右手边的那些AFX_  常数又定义于AFXIMPL.H  中:

#ifndef _UNICODE

#define _UNICODE_SUFFIX

#else

#define _UNICODE_SUFFIX _T("u")

#endif

#ifndef _DEBUG

#define _DEBUG_SUFFIX

#else

#define _DEBUG_SUFFIX _T("d")

#endif

#ifdef _AFXDLL

#define _STATIC_SUFFIX

#else

#define _STATIC_SUFFIX _T("s")

#endif

#define AFX_WNDCLASS(s) \

  _T("Afx") _T(s) _T("42") _STATIC_SUFFIX _UNICODE_SUFFIX _DEBUG_SUFFIX

#define AFX_WND                AFX_WNDCLASS("Wnd")

#define AFX_WNDCONTROLBAR    AFX_WNDCLASS("ControlBar")

#define AFX_WNDMDIFRAME      AFX_WNDCLASS("MDIFrame")

#define AFX_WNDFRAMEORVIEW   AFX_WNDCLASS("FrameOrView")

#define AFX_WNDOLECONTROL    AFX_WNDCLASS("OleControl")

所以,如果在Windows 95(non-Unicode)中使用MFC 动态联结版和除错版,五个窗口类别的名称将是:

"AfxWnd42d"

"AfxControlBar42d"

"AfxMDIFrame42d"

"AfxFrameOrView42d"

"AfxOleControl42d"

如果在Windows NT(Unicode 环境)中使用MFC 静态联结版和除错版,五个窗口类别的名称将是:

"AfxWnd42sud"

"AfxControlBar42sud"

"AfxMDIFrame42sud"

"AfxFrameOrView42sud"

"AfxOleControl42sud"

这五个窗口类别的使用时机为何?稍后再来一探究竟。

让我们再回顾AfxEndDeferRegisterClass  的动作。它调用两个函数完成实际的窗口类别注册动作,一个是RegisterWithIcon ,一个是AfxRegisterClass :

static BOOL AFXAPI RegisterWithIcon(WNDCLASS* pWndCls,

        LPCTSTR lpszClassName, UINT nIDIcon)

{

    pWndCls->lpszClassName = lpszClassName;

    HINSTANCE hInst = AfxFindResourceHandle(

            MAKEINTRESOURCE(nIDIcon), RT_GROUP_ICON);

    if ((pWndCls->hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDIcon))) == NULL)

    {

        // use default icon

        pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);

    }

    return AfxRegisterClass(pWndCls);

}

BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)

{

    WNDCLASS wndcls;

if (GetClassInfo(lpWndClass->hInstance,

                     lpWndClass->lpszClassName, &wndcls))

    {

        // class already registered

        return TRUE;

    }

    ::RegisterClass (lpWndClass);

    ...

    return TRUE;

}

注意,不同类别的PreCreateWindow 成员函数都是在窗口产生之前一刻被调用,准备用来注册窗口类别。如果我们指定的窗口类别是NULL,那么就使用系统预设类别。从CWnd及其各个衍生类别的PreCreateWindow 成员函数可以看出,整个Framework  针对不同功能的窗口使用了哪些窗口类别:

// in WINCORE.CPP
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)

{

    {

        AfxDeferRegisterClass(AFX_WND_REG);

        ...
        cs.lpszClass =  _afxWnd;   (这表示CWnd 使用的窗口类别是_afxWnd)

    }

    return TRUE;

}

// in WINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)

{

    if (cs.lpszClass == NULL)

    {

        AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);

        ...

        cs.lpszClass =  _afxWndFrameOrView; (这表示CFrameWnd 使用的窗口类别是_afxWndFrameOrView)

    }                                           

    ...

}

// in WINMDI.CPP
BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)

{

    if (cs.lpszClass == NULL)

    {

        AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG);

        ...

        cs.lpszClass =  _afxWndMDIFrame;  (这表示C M D I F r a m e W n d  使用的窗口类别是_afxWndMDIFrame)

    }                                         

    return TRUE;

}

// in WINMDI.CPP
BOOL CMDIChildWnd::PreCreateWindow(CREATESTRUCT& cs)

{

    ...

    return CFrameWnd::PreCreateWindow(cs);   (这表示CMDIChildWnd  使用的窗口类别_afxWndFrameOrView)

}                                                

// in VIEWCORE.CPP
BOOL CView::PreCreateWindow(CREATESTRUCT & cs)

{

    if (cs.lpszClass == NULL)

    {

        AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);

        ...        

        cs.lpszClass =  _afxWndFrameOrView; (这表示CView  使用的窗口类别是_afxWndFrameOrView)

    }                                               

    ...

}





奇怪的窗口类别名称 Afx:b:14ae:6:3e8f

当应用程序调用CFrameWnd::Create(或CMDIFrameWnd:: LoadFrame ,第7章)准备产生窗口时,MFC 才会在Create  或LoadFrame  内部所调用的PreCreateWindow 虚拟函式中为你产生适当的窗口类别。你已经在上一节看到了,这些窗口类别的名称分别是(假设在Win95 中使用MFC 4.2 动态联结版和除错版):

"AfxWnd42d"

"AfxControlBar42d"

"AfxMDIFrame42d"

"AfxFrameOrView42d"

"AfxOleControl42d"

然而,当我们以Spy++ (VC++  所附的一个工具)观察窗口类别的名称,却发现:

窗口显示与更新



CMyFrameWnd::CMyFrameWnd 结束后,窗口已经诞生出来;程序流程又回到CMyWinApp::InitInstance ,于是调用ShowWindow 函数令窗口显示出来,并调用UpdateWindow 函数令Hello 程序送出WM_PAINT 消息。我们很关心这个WM_PAINT 消息如何送到窗口函数的手中。而且,窗口函数又在哪里?MFC 程序是不是也像SDK 程序一样,有一个GetMessage/DispatchMesage
循环?是否每个窗口也都有一个窗口函数,并以某种方式进行消息的判断与处理?两者都是肯定的。我们马上来寻找证据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: