The Analysis Of Basic MFC Program Running Principle

Similar documents
MFC 2/e PDF GBK mirror - anyway solution MFC 1/e MFC 2/e

Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc

epub83-1

Windows XP

概述

ebook50-15

INTRODUCTION TO COM.DOC

Microsoft PowerPoint - ch6 [相容模式]

Windows RTEMS 1 Danilliu MMI TCP/IP QEMU i386 QEMU ARM POWERPC i386 IPC PC104 uc/os-ii uc/os MMI TCP/IP i386 PORT Linux ecos Linux ecos ecos eco

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

Computer Architecture

ebook140-8

穨control.PDF

Bus Hound 5

ebook50-14

WWW PHP

Microsoft Word - template.doc

TX-NR3030_BAS_Cs_ indd

C/C++ - 字符输入输出和字符确认

ebook50-11

FY.DOC

软件测试(TA07)第一学期考试

ebook140-9

BOOL EnumWindows(WNDENUMPROC lparam); lpenumfunc, LPARAM (Native Interface) PowerBuilder PowerBuilder PBNI 2

IP505SM_manual_cn.doc

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++;

1.ai

3.1 num = 3 ch = 'C' 2

Microsoft Word - 11.doc

Microsoft Word - CIN-DLL.doc

RUN_PC連載_12_.doc

Chapter 2

PTS7_Manual.PDF

新・解きながら学ぶJava

mvc

C/C++ - 函数

RunPC2_.doc

ebook

AL-MX200 Series

AL-M200 Series

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

untitled

Logitech Wireless Combo MK45 English

提问袁小兵:

Serial ATA ( Silicon Image SiI3114)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 5 (4) S A T A... 8 (5) S A T A... 10

untitled

无类继承.key

27 :OPC 45 [4] (Automation Interface Standard), (Costom Interface Standard), OPC 2,,, VB Delphi OPC, OPC C++, OPC OPC OPC, [1] 1 OPC 1.1 OPC OPC(OLE f

Microsoft Word - HSK使用手册.doc

<4D F736F F D C4EAC0EDB9A4C0E04142BCB6D4C4B6C1C5D0B6CFC0FDCCE2BEABD1A15F325F2E646F63>

HCD0174_2008

A Preliminary Implementation of Linux Kernel Virus and Process Hiding

國立中山大學學位論文典藏.PDF

coverage2.ppt

Microsoft Word - 3D手册2.doc

bingdian001.com

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM

VB程序设计教程

LSC操作说明

Improved Preimage Attacks on AES-like Hash Functions: Applications to Whirlpool and Grøstl

Panaboard Overlayer help

Strings

K301Q-D VRT中英文说明书141009

Microsoft Word - 01.DOC

ch_code_infoaccess

K7VT2_QIG_v3

( Version 0.4 ) 1

IBM Rational ClearQuest Client for Eclipse 1/ IBM Rational ClearQuest Client for Ecl

Value Chain ~ (E-Business RD / Pre-Sales / Consultant) APS, Advanc

VASP应用运行优化

Simulator By SunLingxi 2003

Olav Lundström MicroSCADA Pro Marketing & Sales 2005 ABB - 1-1MRS755673

슬라이드 1

C/C++程序设计 - 字符串与格式化输入/输出

國家圖書館典藏電子全文

RUN_PC連載_10_.doc

錄...1 說...2 說 說...5 六 率 POST PAY PREPAY DEPOSIT 更

Preface This guide is intended to standardize the use of the WeChat brand and ensure the brand's integrity and consistency. The guide applies to all d

bingdian001.com

WTO

Guide to Install SATA Hard Disks

Microsoft PowerPoint - string_kruse [兼容模式]


入學考試網上報名指南

Microsoft Word - ch04三校.doc

Microsoft PowerPoint - STU_EC_Ch08.ppt

C++ 程式設計

Chn 116 Neh.d.01.nis

國 立 政 治 大 學 教 育 學 系 2016 新 生 入 學 手 冊 目 錄 表 11 國 立 政 治 大 學 教 育 學 系 博 士 班 資 格 考 試 抵 免 申 請 表 論 文 題 目 申 報 暨 指 導 教 授 表 12 國 立 政 治 大 學 碩 博 士 班 論

(Guangzhou) AIT Co, Ltd V 110V [ ]! 2

els0xu_zh_nf_v8.book Page Wednesday, June, 009 9:5 AM ELS-0/0C.8

Transcription:

浅析 MFC 程序基本运行机制 或许我不该写这篇文章 ; 或许你会不屑的看了看标题, 然后华丽的 WS 之 ; 又或许你会在看完之后, 在 这篇文章的末尾的写上 打倒 KC, 打倒 MFC, 打倒 M$, 然后签上自己伟大的名字 但是这都丝毫不会影响包括我在内的所有想了解 MFC 的 Coder 对于 MFC 研究 或许, 有好几个问题曾连续地在你的脑海里浮现 : MFC 很容易学么? 是的, 很容易 但是前提是你首先得理解他的源代码, 并且看懂背后的故事 MFC 很复杂么? 是的, 以 MFC4.X 来说, 仅是一个单独的源文件都有超过 120000 行的代码, 这还不算头文件和.H 扩展文件 MFC 很强大么? 是的, 它不仅能让你更加了解 Windows 系统的运行机理, 还能让你从传统 SDK 的束缚中解脱出来 MFC 很恶心么? 是的, 它会时常让你感到, 不是你在控制 MFC, 而是 MFC 在控制你 MFC 很完美么? 没有完美的思想, 也没有完美的程序 从产生人类文明至今, 尚未出现真正称得上 完美 的东西 因为我们在进步, 在革命 常言道 : 知己知彼, 百战不殆 如果你真的决定使用 MFC, 那么你就应该好好的研究它的内部运行机制 这不是关键性的, 但是是必要性的 而这篇文章, 就向大家展示了 MFC 程序的基本运行机制 1. 温故知新 在研究 MFC 的基本运行机制之前, 先让我们来回忆一下使用 C++/SDK 写 Windows GUI 程序的顺序 : 调用 WinMain 入口函数 注册窗口类 窗口实例化 建立消息循环 处理消息 可以说, 几乎每个 Windows GUI 程序的建立和运行, 都要经过上面的几个步骤,MFC 程序也不例外 但 是由于 MFC 是以 C++ 为基础, 所以它势必会使用 OOP 思想进行架构 而这一切, 都会导致我们研究 MFC 的方式会和 C++/SDK 有那么一点区别 我们在下面会以 MFC 的 Class 为中心进行研究, 而非 Windows 窗体的线性行为 这意味着我们得忍受在 几个类中跳来跳去 是的, 你可能会感到身体不适, 我同样有这种感觉, 我从小就恨透了 goto 那么, 就让我们先来看看使用 MFC 改如何创建一个简单的窗体, 然后在逐步抽丝剥茧, 剖析 MFC 程序 的基本运行机理 /************************************************** ** Project:MFCAppUser ** File:MFCAppUser.cpp

** Edition:NULL ** Coder:KingsamChen [MDSA Group] ** Last Modify:2008-8-9 **************************************************/ #include <afxwin.h> // 必备的头文件, 这个头文件间接包含了 windows.h class CMFCApp : public CWinApp // 继承 CWinApp public: virtual BOOL InitInstance(); // 虚函数 // 这个函数必须重写 ; class CMFCAppWindow : public CFrameWnd public: CMFCAppWindow() // 在构造函数里创建窗体 ~ Create(NULL,"KC's Windows"); // 除前两个参数外, 其他参数均有初始值 // 下面是消息映射的东东 afx_msg void OnLButtonDblClk(UINT nflags, CPoint point); // 左键双击的消息声明 afx_msg void OnPaint(); // WM_PAINT 消息声明 DECLARE_MESSAGE_MAP() // 消息映射宏 ; // MS 叫做消息映射表 BEGIN_MESSAGE_MAP(CMFCAppWindow, CFrameWnd) ON_WM_LBUTTONDBLCLK() ON_WM_PAINT() END_MESSAGE_MAP() // 对应的消息处理, 类似 SDK 窗体里面的回调函数处理过程 void CMFCAppWindow::OnLButtonDblClk(UINT nflags, CPoint point) MessageBox("KC is a Fucker", NULL, MB_OK); void CMFCAppWindow::OnPaint() CPaintDC Paint(this); Paint.TextOut(0, 0, "This is a sample for MFC");

// 重写 InitInstance 虚函数 BOOL CMFCApp::InitInstance() m_pmainwnd = new CMFCAppWindow(); m_pmainwnd->showwindow(m_ncmdshow); m_pmainwnd->updatewindow(); return TRUE; CMFCApp theapp; // 唯一的应用程序对象 将这段代码编译运行后 ( 注意将编译方式调整为 使用静态的 MFC 库 ), 可以看到运行了一个窗体, 双 击窗体会弹出一个 MessageBox 这里的代码要比使用 C++/SDK 要简单明了的多, 但是我们没有发现熟悉的 WinMain, 也没有窗口注册创 建等等的部分 显然, 这些都被 MFC 隐藏了 2. CWinApp 应用程序类 就像标题所说的,CWinApp 的作用是用来产生 MFC 应用程序对象 每个 MFC 程序都要求存在一个派生 自 CWinApp 的子类作为应用程序的类, 并且需要我们通过这个子类, 改写某些虚函数 可见 CWinApp 的 重要性, 显然,CWinApp 是一个很大的类, 他得完成很多工作 以下是 MFC7.1 的 CWinApp 的定义源代码的超精简版本 ( 完整版在 AFXWIN.H 中 ): class CWinApp : public CWinThread DECLARE_DYNAMIC(CWinApp) public: // Constructor /* explicit */ CWinApp(LPCTSTR lpszappname = NULL); // app name defaults to EXE name // Attributes // Startup args (do not change) // This module's hinstance. HINSTANCE m_hinstance; // Pointer to the command-line. LPTSTR m_lpcmdline; // Initial state of the application's window; normally,

// this is an argument to ShowWindow(). int m_ncmdshow; // Running args (can be changed in InitInstance) // Human-redable name of the application. Normally set in // constructor or retreived from AFX_IDS_APP_TITLE. LPCTSTR m_pszappname; // Name of registry key for this application. See // SetRegistryKey() member function. LPCTSTR m_pszregistrykey; // Pointer to CDocManager used to manage document templates // for this application instance. CDocManager* m_pdocmanager; // Support for Shift+F1 help mode. // TRUE if we're in SHIFT+F1 mode. BOOL m_bhelpmode; public: // set in constructor to override default // Executable name (no spaces). LPCTSTR m_pszexename; // Default based on this module's path. LPCTSTR m_pszhelpfilepath; // Default based on this application's name. LPCTSTR m_pszprofilename; // Overridables // Hooks for your initialization code virtual BOOL InitApplication(); public: // public for implementation access CCommandLineInfo* m_pcmdinfo; // overrides for implementation virtual BOOL InitInstance(); virtual int ExitInstance(); // return app exit code virtual int Run(); virtual BOOL OnIdle(LONG lcount); // return TRUE if more idle processing

virtual LRESULT ProcessWndProcException(CException* e, const MSG* pmsg); virtual HINSTANCE LoadAppLangResourceDLL(); public: virtual ~CWinApp(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif // helpers for registration HKEY GetSectionKey(LPCTSTR lpszsection); HKEY GetAppRegistryKey(); protected: //AFX_MSG(CWinApp) afx_msg void OnAppExit(); afx_msg void OnUpdateRecentFileMenu(CCmdUI* pcmdui); afx_msg BOOL OnOpenRecentFile(UINT nid); //AFX_MSG DECLARE_MESSAGE_MAP() public : // System Policy Settings virtual BOOL LoadSysPolicies(); // Override to load policies other than the system policies that MFC loa ds. BOOL GetSysPolicyValue(DWORD dwpolicyid, BOOL *pbvalue); // returns the policy's setting in the out parameter protected : DWORD m_dwpolicies; // block for storing boolean system policies ; CWinApp 中全是成员函数和成员变量 ( 废话 - -!), 我们重点来看看几个重要的函数和变量 : CWinApp 中保存了一些传递给 WinMain() 的命令行参数, 这些参数包括当前实例的句柄 ( m_hinstance) 前一个实例的句柄 (m_hprevinstance) 命令行参数 (m_lpcmdline) 以及控制窗口显示的标志 (m_ncmdshow) CWinApp 在 m_pszappname 中保存了应用程序名字的拷贝 CWinApp 中保存了指向可执行文件名字的指针 (m_pszexename) 指向应用程序帮助文件路径的指针 (m_pszhelpfilepath) 以及指向其应用程序配置文件名字的指针 (m_pszprofilename) CWinApp 还是用了一个叫做 CCommandLineInfo 的 Class 来保存命令行参数 CCommandLineInfo 在一个地方保存了所有标准参数, 他是一个很小却很实用的 Class 他在你需要得到的命令行参数的时候很有用 我们这里粗略的浏览下他的定义源代码 ( 同样在 AFXWIN.H 中 ): class CCommandLineInfo : public CObject public: // Sets default values CCommandLineInfo();

// plain char* version on UNICODE for source-code backwards compatibility virtual void ParseParam(const TCHAR* pszparam, BOOL bflag, BOOL blast); #ifdef _UNICODE virtual void ParseParam(const char* pszparam, BOOL bflag, BOOL blast); #endif BOOL m_bshowsplash; BOOL m_brunembedded; BOOL m_brunautomated; enum FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, AppRegister, AppUnregister, FileNothing = -1 m_nshellcommand; // not valid for FileNew CString m_strfilename; // valid only for FilePrintTo CString m_strprintername; CString m_strdrivername; CString m_strportname; ~CCommandLineInfo(); // Implementation protected: void ParseParamFlag(const char* pszparam); void ParseParamNotFlag(const TCHAR* pszparam); #ifdef _UNICODE void ParseParamNotFlag(const char* pszparam); #endif void ParseLast(BOOL blast); ; 每个应用程序都需要进行针对实例的初始化 在 MFC 程序中, 这部分功能由 CWinApp::InitInstance() 完成 这个函数是虚函数, 我们在派生类中必须重写这个函数 一般会在这个函数里面显示主窗口 在 MFC 程序中, 可以通过 CWinApp::ExitInstance() 来完成关闭应用程序时候的资源清理工作 这个函数同样是虚函数 在 MFC 程序中, 消息泵是 CWinApp 的一部分 调用 CWinApp::Run() 会启动标准的 GetMessage()..DispatchMessage() 循环 CWinApp 的消息还支持后台处理 由于 Windows 使用事件驱动机制, 所以应用程序运行期间队列中可能没有任何消息 MFC 此时会调用 CWinApp 的 OnIdle() 函数 每当消息队列为空时,CWinApp::Run() 都会调用 OnIdle() 可能你会觉得这里缺少了什么东西, 比如保存主窗口句柄的成员变量 事实上, 的确有这个成员变量, 变量名为 m_pmainwnd 如果你翻到前面的例子代码, 你会发现这个变量的用途 传说在很久之前的 MFC 版本,m_pMainWnd 被安排在了 CWinApp 里, 但是经过几经周折, 现在, 这个 成员变量升级了, 跑到了 CWinThread 里去了 (CWinThread 是 CWinApp 的父类 ) CWinThread 的源代码也 在 AFXWIN.H 中

class CWinThread : public CCmdTarget DECLARE_DYNAMIC(CWinThread) friend BOOL AfxInternalPreTranslateMessage(MSG* pmsg); public: // Constructors CWinThread(); BOOL CreateThread(DWORD dwcreateflags = 0, UINT nstacksize = 0, LPSECURITY_ATTRIBUTES lpsecurityattrs = NULL); // Attributes // Look at Here! CWnd* m_pmainwnd; CWnd* m_pactivewnd; BOOL m_bautodelete;... // main window (usually same AfxGetApp()->m_pMainWnd) // active main window (may not be m_pmainwnd) // enables 'delete this' after thread termination 3. CFrameWnd 框架窗口类 我不想花费过多的时间在这个问题上 是的正如标题所说,CFrameWnd 只是 MFC 窗口类的一种 CFrameWnd 能提供 SDI Overlapped Pop-up 风格的窗口 但是对于 MFC 来说, 窗口的种类远不止这些 如果你打开 MFC 的类库, 你会发现 MFC 的窗口类有 :Frame Window Dialog Boxes Views Controls 而能作为 Main Window 的窗口类也不止一种 ( 事实上, 以 Dialog 作为 Main Window 的情况更加常见 ) 不同的窗口类之间都有差别, 但是他们都有共同的地方 是的, 正如你所见, 他们都继承自 CWnd CWnd 是一个很巨大的类, 他是所有窗口类的基类 由于继承的特性, 子类都会强制保留一些属性 虽然各个窗口类间的实现存在区别, 但在本质上, 他们都完成了非常类似的工作 所以, 我们通过采取 对 CFrameWnd 这一特殊情况进行研究, 从而一般化窗口类的实现过程 ( 不过有时候, 这个一般化过程会 因为某些窗口的自身的特性而遭遇阻碍 ) CFrameWnd 的主要作用是创建一个窗体对象, 并通过自身的消息映射表 (Message Map), 将消息映射 到相应的消息处理函数中进行处理 这里 CFrameWnd 充当了 SDK 中回调函数的职责 4. theapp 唯一的应用程序对象 这里, 我们先看看开头的例子代码, 并把目光集中到 CMFCApp theapp; // 唯一的应用程序对象

我们用派生自 CWinApp 的应用程序类 CMFCApp 创建了一个全局对象 theapp theapp 就是我们的应用程序对象 一般的, 每个 MFC 程序都有这个对象, 而且有且只有一个 ( 通常用 MFC 向导生成的工程的应用程序对象名都是 theapp) 按照惯例, 创建 theapp 时会先调用父类 CWinApp 的构造函数, 然后调用 CMFCWinApp 的构造函数 但 是由于我们没有定义子类的构造函数, 所以只会执行 CWinApp 的构造函数 那么接下来, 我们就通过源代 码, 看看 CWinApp 在构造函数里都干了什么事情 ( 代码在 appcore.cpp 中 ): 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_hlangresourcedll = 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_ehelptype = afxwinhelp; m_nsafetypoolsize = 512; // default size CWinApp 的构造函数主要工作就是初始化 CWinApp 的成员变量 构造函数首先通过参数 lpszappname 设置 m_pszappname 的值 参数默认为 NULL, 你也可以传入应用 程序的名字 然后通过 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(); 初始化线程状态和模块状态 因为线程状态要牵扯到 MFC 的线程运作, 这里不作介绍 AFX_MODULE_STATE 之后进行介绍 紧接着, 构造函数会初始化 CWinApp 的状态 ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please pmodulestate->m_pcurrentwinapp = this; ASSERT(AfxGetApp() == this); 这里第一 二行和 AFX_MODULE_STATE 有关, 我们待会再讲 这里的 AfxGetApp() 返回与程序相关的应用程序对象 ( 每一个 MFC 程序都需要一个 CWinApp 的派生对象 ), 用于检查对象是否正确 然后,CWinApp 将所有其他成员 ( 句柄 指针和字符串等 ) 都设置成 NULL 4.1. AFX_MODULE_STATE MFC 状态信息

对于一个应用程序来说, 在运行期间保存某些信息是应该的 ( 或许也是必须的 ) 比如,SDK 程序会将实例句柄保存为全局变量 而程序就可以通过这个实例句柄获得 EXE 文件的资源 MFC 也酱紫做, 不过, 它会通过 Class, 来保存各种资源 和 SDK 程序相比,MFC 除了实例句柄之外, 还有其他很多需要保存的信息 可能会包括窗口 资源 模块句柄 而 MFC 能为应用程序提供内存分配的跟踪 ( 这个 MS 很好很强大 ) ODBC 支持 OLE 支持和异常处理等特性, 所以 MFC 程序会比一般的 SDK 程序维护更多的信息 于是,MFC 定义了一个叫做 AFX_MODULE_STATE 的 Class, 我们先来看看他的定义源代码 ( 在 AFXSTAT_.H 中 ): class AFX_MODULE_STATE : public CNoTrackObject public: #ifdef _AFXDLL AFX_MODULE_STATE(BOOL bdll, WNDPROC pfnafxwndproc, DWORD dwversion, BOOL bsystem = FALSE); #else explicit AFX_MODULE_STATE(BOOL bdll); #endif ~AFX_MODULE_STATE(); CWinApp* m_pcurrentwinapp; HINSTANCE m_hcurrentinstancehandle; HINSTANCE m_hcurrentresourcehandle; LPCTSTR m_lpszcurrentappname; BYTE m_bdll; // TRUE if module is a DLL, FALSE if it is an EXE BYTE m_bsystem; // TRUE if module is a "system" module, FALSE if not BYTE m_breserved[2]; // padding DWORD m_fregisteredclasses; // flags for registered window classes // runtime class data #ifdef _AFXDLL CRuntimeClass* m_pclassinit; #endif CTypedSimpleList<CRuntimeClass*> m_classlist; // OLE object factories #ifndef _AFX_NO_OLE_SUPPORT #ifdef _AFXDLL COleObjectFactory* m_pfactoryinit; #endif CTypedSimpleList<COleObjectFactory*> m_factorylist; #endif // number of locked OLE objects long m_nobjectcount; BOOL m_buserctrl;

// AfxRegisterClass and AfxRegisterWndClass data TCHAR m_szunregisterlist[4096]; #ifdef _AFXDLL WNDPROC m_pfnafxwndproc; DWORD m_dwversion; // version that module linked against #endif // variables related to a given process in a module // (used to be AFX_MODULE_PROCESS_STATE) void (PASCAL *m_pfnfiltertooltipmessage)(msg*, CWnd*); #ifdef _AFXDLL // CDynLinkLibrary objects (for resource chain) CTypedSimpleList<CDynLinkLibrary*> m_librarylist; // special case for MFC71XXX.DLL (localized MFC resources) HINSTANCE m_applangdll; #endif #ifndef _AFX_NO_OCC_SUPPORT // OLE control container manager COccManager* m_poccmanager; // locked OLE controls CTypedSimpleList<COleControlLock*> m_locklist; #endif #ifndef _AFX_NO_DAO_SUPPORT _AFX_DAO_STATE* m_pdaostate; #endif #ifndef _AFX_NO_OLE_SUPPORT // Type library caches CTypeLibCache m_typelibcache; CTypeLibCacheMap* m_ptypelibcachemap; #endif ; // define thread local portions of module state CThreadLocal<AFX_MODULE_THREAD_STATE> m_thread; 首先我们会发现一个很有意思的情况 : 所有的成员都是公有的, 这样就可以直接访问成员变量而不需通 过成员函数 然后, 我们来看下 AFX_MODULE_STATE 中一些重要的成员变量 : CWinApp* m_pcurrentwinapp;

HINSTANCE m_hcurrentinstancehandle; HINSTANCE m_hcurrentresourcehandle; LPCTSTR m_lpszcurrentappname; BYTE m_bdll; // TRUE if module is a DLL, FALSE if it is an EXE BYTE m_bsystem; // TRUE if module is a "system" module, FALSE if not DWORD m_fregisteredclasses; // flags for registered window classes // runtime class data m_classlist; m_factorylist // number of locked OLE objects long m_nobjectcount; BOOL m_buserctrl; // AfxRegisterClass and AfxRegisterWndClass data m_szunregisterlist[4096]; m_pfnafxwndproc m_pcurrentwinapp 指向 CWinApp 的指针 现在再回头看 CWinApp 的构造函数, 我们使用了下面的代码 pmodulestate->m_pcurrentwinapp = this; 将 m_pcurrentwinapp 初始化为正在构造的 CWinApp 对象 而 ASSERT(afxCurrentWinApp == NULL); 则可以检查是否已经存在一个应用程序对象, 从而保证了 theapp 的唯一性 (afxcurrentwinapp 是一个宏, 用来返回 m_pcurrentwinapp) m_hcurrentinstancehandle 该模块实例的句柄 别用这个来获得本地资源 m_hcurrentresourcehandle 所保存模块资源的句柄 可以使用这个来获得资源, 有助于你的东西走向国 际市场 - -! m_lpszcurrentappname 指向应用程序名字的指针 m_bdll 表示模块是一个 DLL 还是 EXE m_bsystem 表示模块是系统模块还是本地模块 m_fregisteredclasses 指示哪些 MFC 窗口类已经注册 ( 关于 MFC 注册窗口类, 我们会在后面说到 ) m_classlist 指向 MFC 程序 CRuntimeClass 结构链表的第一个运行时类的指针 m_factorylist 指向 MFC 程序 COleObjectFactory 结构链表中的一个运行时类的指针 m_nobjectcount OLE 服务器的引用计数 会被用来检查是否有未完成的 COM 对象 m_buserctrl 表示是否在使用 OLE m_szunregisterlist[4096] 用于维护 MFC 已注册窗口类的链表, 以便于 MFC 在这些类结束时可以将他们 注销 m_pfnafxwndproc MFC 窗口过程的函数指针

现在我们已经了解了 MFC 中保存的状态信息, 现在我们继续探索 MFC 之旅 5. 找回 WinMain() theapp 创建完毕之后, 我们就要开始寻找 WinMain() 了 对于一个 Windows 应用程序来说, 必定存在一 个 WinMain() 而我们的 MFC 程序也能正常运行, 所以 WinMain 肯定被埋藏在了某个地方 ( 事实上,MFC 会在编译链接的时候直接加到应用程序代码中 ) 现在, 我们来看看 MFC 中包含的 WinMain()( 代码在 APPMODUL.CPP 中 ) // export WinMain to force linkage to this module extern int AFXAPI AfxWinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow); extern "C" int WINAPI _twinmain(hinstance hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow) // call shared/exported WinMain return AfxWinMain(hInstance, hprevinstance, lpcmdline, ncmdshow); 样 我们可以看到,WinMain() 把处理交给了 AfxWinMain() 的函数 而且这个函数的参数和 WinMain() 一模一 顺便说下, 这里的 AFXAPI 和以前的 WINAPI 一样, 都是调用约定 我们现在就来看看 AfxWinMain() 的源代码, 看看他都做了什么事情 ( 代码在 WINMAIN.CPP): #ifdef AFX_CORE1_SEG #pragma code_seg(afx_core1_seg) #endif ///////////////////////////////////////////////////////////////////////////// // Standard WinMain implementation // Can be replaced as long as 'AfxWinInit' is called first 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) TRACE(traceAppMsg, 0, "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) TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n", AfxGetModuleThreadState()->m_nTempMapLock); AfxLockTempMaps(); AfxUnlockTempMaps(-1); #endif AfxWinTerm(); return nreturncode; 我们先看 CWinThread* pthread = AfxGetThread(); 和 CWinApp* papp = AfxGetApp(); 前者返回一个代表当前执行线程的指针 ( 因为牵扯到 MFC 的线程运作, 所以我们这里不仔细研究 ) 后者我们在研究 CWinApp 的构造函数时已经碰到了, 他会返回指向当前程序的应用程序对象指针 但是 不知道大家会不会有个疑问 : 如果 WinMain() 仍然是 MFC 程序的函数入口点, 而应用程序又不是在 WinMain() 中所创建的, 那么如何返回应用程序对象? 其实道理很简单, 因为我们的 theapp 是全局对象 而 C++ 程序在做任何事情之前 ( 不知道是不是绝对了

点 ), 甚至是进入 main() 或 WinMain() 之前, 首先创建全局对象 这样就可以保证进入 WinMain() 的时候, 应用程序对象已经创建 然后我们先暂时跨过 AfxWinInit(), 先来研究几个重要的 表演 CWinThread* pthread = AfxGetThread(); CWinApp* papp = AfxGetApp(); papp->initapplication(); pthread->initinstance(); pthread->run(); 上面的代码类似于 : CMFCApp::InitApplication(); CMFCApp::Instance(); CMFCApp::Run(); 因为我们有一个派生自 CWinApp 的类 CMFCApp, 而由于多态特性的存在 ( 上面的几个函数都是虚函数 ), 上面的代码会导致下面情况的发生 : CWinApp::InitApplication(); // 未改写这个虚函数 CMFCApp::Instance(); // 因为我们改写了这个虚函数 CWinApp::Run(); // 未改写这个虚函数 PS: 其实在 MFC7.X 的版本中,InitInstance Run ExitInstance 和 OnIdle 成员函数实际位于 CWinThread 类中 而 CWinApp 继承了 CWinThread 的这些函数, 并且也部分重写了这几个虚函数, 所以当我们没有重 写虚函数时,C++ 就会往继承树上翻, 便会执行 CWinApp 中的几个虚函数 6. AfxWinInit() 初始化框架 刚才我们跳过了 AfxWinInit() 函数, 现在我们回过头来看看 AfxWinInit() 函数由 MFC 提供的 WinMain() 函数调用, 用于初始化 MFC 框架 我们通过 AfxWinInit() 函数的 源代码, 来看看这个函数都做了哪些事 ( 源代码在 APPINIT.CPP): BOOL AFXAPI AfxWinInit(HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow) ASSERT(hPrevInstance == NULL); // handle critical errors and avoid Windows message boxes SetErrorMode(SetErrorMode(0) SEM_FAILCRITICALERRORS SEM_NOOPENFILEERRORBOX); // set resource handles

AFX_MODULE_STATE* pmodulestate = AfxGetModuleState(); pmodulestate->m_hcurrentinstancehandle = hinstance; pmodulestate->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; hprevinstance; // Obsolete. papp->m_lpcmdline = lpcmdline; papp->m_ncmdshow = ncmdshow; papp->setcurrenthandles(); // initialize thread specific data (for main thread) if (!afxcontextisdll) AfxInitThread(); // Initialize CWnd::m_pfnNotifyWinEvent HMODULE hmodule = ::GetModuleHandle(_T("user32.dll")); if (hmodule!= NULL) CWnd::m_pfnNotifyWinEvent = (CWnd::PFNNOTIFYWINEVENT)::GetProcAddress(hModule, "NotifyWinEvent" ); return TRUE; AfxWinInit() 的 4 个参数和 WinMain() 所带的一样 AfxWinInit() 首先会调用 SetErrorMode() 来为应用程序设置错误模式, 用于指明导致程序失败的原因 SetErrorMode() 的参数只有一个 : 指定错误模式 MFC 使用 SEM_FAILCRITICALERRORS 和 SEM_NOOPENFILEERRORBOX 设置错误模式 前者用于通知窗口对于关键错误 (Critical-Error) 不要显示关键错误处理消息框 (the critical-error-handler message box), 而是将错误传回调用的进程 后者则告诉窗体在没有找到文件时不要显示消息框, 而是将错误传回调用的进程 PS: 关于 SetErrorMode() 的详细信息请看附录 接下来, 我们看这里 AFX_MODULE_STATE* pmodulestate = AfxGetModuleState(); pmodulestate->m_hcurrentinstancehandle = hinstance; pmodulestate->m_hcurrentresourcehandle = hinstance;

AfxWinInit() 会调用 AfxGetModuleState() 来得到 AFX_MODULE_STATE AfxWinInit() 将模块的实例句柄和资源句柄存放在 AFX_MODULE_STATE:: m_hcurrentinstancehandle 和 AFX_MODULE_STATE:: m_hcurrentresourcehandle 中 然后我们看这里 : CWinApp* papp = AfxGetApp(); if (papp!= NULL) // Windows specific initialization (not done if no CWinApp) papp->m_hinstance = hinstance; hprevinstance; // Obsolete. papp->m_lpcmdline = lpcmdline; papp->m_ncmdshow = ncmdshow; papp->setcurrenthandles(); 这里创建了一个指向当前应用程序对象的 CWinApp 指针, 然后设置 Windows 传递给程序的 4 个参数 然后最有意思的是最后一句 papp->setcurrenthandles(); 我们先来看看这个函数的源代码 ( 同样在 APPINIT.CPP 中 ): void CWinApp::SetCurrentHandles() ASSERT(this == afxcurrentwinapp); ASSERT(afxCurrentAppName == NULL); AFX_MODULE_STATE* pmodulestate = _AFX_CMDTARGET_GETSTATE(); pmodulestate->m_hcurrentinstancehandle = m_hinstance; pmodulestate->m_hcurrentresourcehandle = m_hinstance; // Note: there are a number of _tcsdup (aka strdup) calls that are // made here for the exe path, help file path, etc. In previous // versions of MFC, this memory was never freed. In this and future // versions this memory is automatically freed during CWinApp's // destructor. If you are freeing the memory yourself, you should // either remove the code or set the pointers to NULL after freeing // the memory. // get path of executable TCHAR szbuff[_max_path]; DWORD dwret = ::GetModuleFileName(m_hInstance, szbuff, _MAX_PATH); ASSERT( dwret!= 0 && dwret!= _MAX_PATH ); if( dwret == 0 dwret == _MAX_PATH ) AfxThrowUserException();

LPTSTR lpszext = ::PathFindExtension(szBuff); ASSERT(lpszExt!= NULL); if( lpszext == NULL ) AfxThrowUserException(); ASSERT(*lpszExt == '.'); *lpszext = 0; // no suffix TCHAR szexename[_max_path]; TCHAR sztitle[256]; // get the exe title from the full path name [no extension] dwret = AfxGetFileName(szBuff, szexename, _MAX_PATH); ASSERT( dwret == 0 ); if( dwret!= 0 ) AfxThrowUserException(); if (m_pszexename == NULL) BOOL benable = AfxEnableMemoryTracking(FALSE); m_pszexename = _tcsdup(szexename); // save non-localized name AfxEnableMemoryTracking(bEnable); // m_pszappname is the name used to present to the user if (m_pszappname == NULL) BOOL benable = AfxEnableMemoryTracking(FALSE); if (AfxLoadString(AFX_IDS_APP_TITLE, sztitle)!= 0) m_pszappname = _tcsdup(sztitle); // human readable title else m_pszappname = _tcsdup(m_pszexename); // same as EXE AfxEnableMemoryTracking(bEnable); pmodulestate->m_lpszcurrentappname = m_pszappname; ASSERT(afxCurrentAppName!= NULL); // get path of.hlp file or.chm (HtmlHelp) file if (m_pszhelpfilepath == NULL) if (m_ehelptype == afxhtmlhelp) lstrcpy(lpszext, _T(".CHM")); else lstrcpy(lpszext, _T(".HLP")); BOOL benable = AfxEnableMemoryTracking(FALSE); m_pszhelpfilepath = _tcsdup(szbuff); AfxEnableMemoryTracking(bEnable);

*lpszext = '\0'; // back to no suffix if (m_pszprofilename == NULL) lstrcat(szexename, _T(".INI")); // will be enough room in buffer BOOL benable = AfxEnableMemoryTracking(FALSE); m_pszprofilename = _tcsdup(szexename); AfxEnableMemoryTracking(bEnable); 在这个函数中, 我们会发现很多有意思的地方 我们先来看这里 : AFX_MODULE_STATE* pmodulestate = _AFX_CMDTARGET_GETSTATE(); pmodulestate->m_hcurrentinstancehandle = m_hinstance; pmodulestate->m_hcurrentresourcehandle = m_hinstance; SetCurrentHandles() 函数中再次设置了 AFX_MODULE_STATE 句柄 你可能会觉得这个有点多余, 实际上这 是一个历史包袱 - -!, 在很久之前的 MFC 版本中, 是没有模块状态结构这个东西的 后来在解决 MFC 中 的 OLE 控件时才引入这个结构 所以, 很多事情都很难说 以后怎么样, 谁也不知道 兴许哪天上校我走在马路上就被车撞成了真正的 Son-Of-Darkness 咳咳 ~MS 有点跑题了 TCHAR szbuff[_max_path]; DWORD dwret = ::GetModuleFileName(m_hInstance, szbuff, _MAX_PATH); ASSERT( dwret!= 0 && dwret!= _MAX_PATH ); if( dwret == 0 dwret == _MAX_PATH ) AfxThrowUserException(); LPTSTR lpszext = ::PathFindExtension(szBuff); ASSERT(lpszExt!= NULL); if( lpszext == NULL ) AfxThrowUserException(); ASSERT(*lpszExt == '.'); *lpszext = 0; // no suffix TCHAR szexename[_max_path]; TCHAR sztitle[256]; // get the exe title from the full path name [no extension] dwret = AfxGetFileName(szBuff, szexename, _MAX_PATH); ASSERT( dwret == 0 ); if( dwret!= 0 ) AfxThrowUserException();

if (m_pszexename == NULL) BOOL benable = AfxEnableMemoryTracking(FALSE); m_pszexename = _tcsdup(szexename); // save non-localized name AfxEnableMemoryTracking(bEnable); 这部分主要用于初始化应用程序的名字和路径 PS: 这里不要忘了前面的 SetErrorMode() 的作用 ~ 首先用 GetModuleFileName 返回模块的运行实例的全名, 然后对函数的返回值进行检查 AfxThrowUserException 会抛出一个异常, 然后结束用户的操作 然后用 PathFindExtension 返回文件的路径 (. 之前的路径 ), 同样对返回值进行检查 ( 后面的那个 AfxGetFileName 用的也是这个 API,AfxGetFileName 源代码就在 SetCurrentHandles() 的下面 ) 取得合法的可执行文件名字后 ( 不包括.exe), 赋值给 CWinApp::m_pszExeName 而 AfxEnableMemoryTracking 用于诊断内存检测 PS:GetModuleFileName PathFindExtension AfxThrowUserException 和 AfxEnableMemoryTracking 的详细 信息请查看 GetModuleFileName 附录 然后看这里 : if (m_pszappname == NULL) BOOL benable = AfxEnableMemoryTracking(FALSE); if (AfxLoadString(AFX_IDS_APP_TITLE, sztitle)!= 0) m_pszappname = _tcsdup(sztitle); // human readable title else m_pszappname = _tcsdup(m_pszexename); // same as EXE AfxEnableMemoryTracking(bEnable); pmodulestate->m_lpszcurrentappname = m_pszappname; ASSERT(afxCurrentAppName!= NULL); SetCurrentHandles() 设置完 CWinApp::m_pszExeName 之后, 将 m_pszappname 初始化为应用程序的标题 如果你使用 MFC Wizard 来创建工程, 那么 MFC 会使用工程中的资源文件中特定的字符串 ID 来代替 AFX_IDS_APP_TITLE 如果找不到资源,SetCurrentHandles() 就会用 m_pszexename 来初始化 m_pszappname 然后 SetCurrentHandles() 将 AFX_MODULE_STATE:: m_lpszcurrentappname 设置为 m_pszappname // get path of.hlp file or.chm (HtmlHelp) file

if (m_pszhelpfilepath == NULL) if (m_ehelptype == afxhtmlhelp) lstrcpy(lpszext, _T(".CHM")); else lstrcpy(lpszext, _T(".HLP")); BOOL benable = AfxEnableMemoryTracking(FALSE); m_pszhelpfilepath = _tcsdup(szbuff); AfxEnableMemoryTracking(bEnable); *lpszext = '\0'; // back to no suffix if (m_pszprofilename == NULL) lstrcat(szexename, _T(".INI")); // will be enough room in buffer BOOL benable = AfxEnableMemoryTracking(FALSE); m_pszprofilename = _tcsdup(szexename); AfxEnableMemoryTracking(bEnable); 最后,SetCurrentHandles() 还会设置 CWinApp 的帮助文件和 Profile 字符串 m_pszhelpfilepath 会被初始化为 GetModuleFileName() 返回的值, 并以 CHM 或 HLP 作为扩展名 m_pszprofilename 会被初始化为应用程序的名字, 并且以 INI 作为扩展名 看完 SetCurrentHandles(), 我们在回过头看 AfxWinInit() 的最后一部分代码 : // initialize thread specific data (for main thread) if (!afxcontextisdll) AfxInitThread(); // Initialize CWnd::m_pfnNotifyWinEvent HMODULE hmodule = ::GetModuleHandle(_T("user32.dll")); if (hmodule!= NULL) CWnd::m_pfnNotifyWinEvent = (CWnd::PFNNOTIFYWINEVENT)::GetProcAddress(hModule, "NotifyWinEvent" ); return TRUE; AfxWinInit() 最后会初始化线程 ( 这部分照例跳过 ~), 然后初始化 CWnd::m_pfnNotifyWinEvent 这个成员 变量看起来 MS 是系统的消息事件

GetProcAddress 返回 DLL 中导出函数的地址 如此这般, 应用程序和框架就完全初始化了 句柄和文件名都正确的被初始化, 然后 AfxWinMain() 会调用应用程序的 InitApplication() 函数 ~ 7. InitApplication 逝去的光环 其实不应该讲这个函数 如果你用的是 MFC7.X, 当你在 CWinApp 中查询 Override Functions 时, 你会发现这个函数消失了 是的, 这个函数已经消失了, 在新版的 MFC 中, 这个函数已经被废除 这个函数是在 Win16 下使用的, 现在到了 Win32, 这个函数已经没用了, 他仅仅完成一些过渡性的工作 所有的初始化动作都在 InitInstance() 中完成, 所以这个函数, 剩下的也只是种种过往 ~ MSDN 对这位英雄人物的归隐是这样描述的 : CWinApp::InitApplication The CWinApp::InitApplication member function is obsolete in MFC. Remarks An initialization that you would have done in InitApplication should be moved to InitInstance. If you override CWinApp::InitApplication, and you do not call the base class function, you will leak the CDocTemplate objects that were added through CWinApp::AddDocTemplate. 叶落归根, 逝者如斯 ~ 这不能不说是一个悲剧 ~ 8. InitInstance 窗体产生的地方 IniApplication 之后,AfxWinMain() 会便调用 InitInstance 函数, 这个函数是个虚函数, 而且我们必须在我 们的派生类里改写这个函数, 因为 CWinApp 的 InitInstance 没有任何创建窗体的行为 而一般我们都会在 改写的这个函数里创建主窗体, 并且设置 CWinThread::m_pMainWnd 变量指向主窗体 因为是调用我们自己的 InitInstance() 函数, 所以我们回过头去看看例子代码中都在这个函数里干了什么 事情 : BOOL CMFCApp::InitInstance() m_pmainwnd = new CMFCAppWindow(); m_pmainwnd->showwindow(m_ncmdshow); m_pmainwnd->updatewindow(); return TRUE;

这里, 我们把 m_pmainwnd 指向了分配的 CMFCAppWindow 类型空间 很明显, 这回调用 CMFCAppWindow 的构造函数, 我们看下这个类的构造函数 : public: CMFCAppWindow() // 在构造函数里创建窗体 ~ Create(NULL,"KC's Windows"); // 除前两个参数外, 其他参数均有初始值 我们在构造函数里用 Create 创建了一个窗体 关于 CFrameWnd::Create() 我不想在这个上面花太多功夫 这个函数除了第一 二个参数之外, 其他参数均有 NULL 缺省值 第一个参数指定 WNDCLASS 类名 ( 如 果你了解 SDK 写法, 应该对这个不陌生 ), 如果为 NULL, 则使用默认的 CFrameWnd 属性来初始化 ; 第二 个指定窗体标题 PS: 关于这个函数的详细信息, 请参考附录 创建完之后, 很类似的, 我们使用了 ShowWindow() 和 UpdateWindow() 来显示并更新窗体 9. 一些秘密 9.1. 注册窗口类 在 Windows 应用程序显示窗口之前, 应用程序必须至少注册一个窗口类 而 MFC 程序也和普通的 Windows 程序一样, 所以他也要至少注册一个窗口类 首先,MFC 在 AFXIMPL.H 中定义了一个 AfxDeferRegisterClass() 的宏 : #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass) 为了深入了解, 我们来看下 AfxEndDeferRegisterClass 的源代码 ( 在 WINCORE.CPP 中 ): BOOL AFXAPI AfxEndDeferRegisterClass(LONG ftoregister) // mask off all classes that are already registered AFX_MODULE_STATE* pmodulestate = AfxGetModuleState(); ftoregister &= ~pmodulestate->m_fregisteredclasses; if (ftoregister == 0) return TRUE; LONG fregisteredclasses = 0; // common initialization WNDCLASS wndcls;

memset(&wndcls, 0, sizeof(wndclass)); // start with NULL defaults wndcls.lpfnwndproc = DefWindowProc; wndcls.hinstance = AfxGetInstanceHandle(); wndcls.hcursor = afxdata.hcurarrow; INITCOMMONCONTROLSEX init; init.dwsize = sizeof(init); // work to register classes as specified by ftoregister, populate fregisteredclasses as we go if (ftoregister & AFX_WND_REG) // Child windows - no brush, no icon, safest default class styles wndcls.style = CS_DBLCLKS CS_HREDRAW CS_VREDRAW; wndcls.lpszclassname = _afxwnd; if (AfxRegisterClass(&wndcls)) fregisteredclasses = AFX_WND_REG; if (ftoregister & AFX_WNDOLECONTROL_REG) // OLE Control windows - use parent DC for speed wndcls.style = CS_PARENTDC CS_DBLCLKS CS_HREDRAW CS_VREDRAW; wndcls.lpszclassname = _afxwndolecontrol; if (AfxRegisterClass(&wndcls)) fregisteredclasses = AFX_WNDOLECONTROL_REG; if (ftoregister & AFX_WNDCONTROLBAR_REG) // Control bar windows wndcls.style = 0; // control bars don't handle double click wndcls.lpszclassname = _afxwndcontrolbar; wndcls.hbrbackground = (HBRUSH)(COLOR_BTNFACE + 1); if (AfxRegisterClass(&wndcls)) fregisteredclasses = AFX_WNDCONTROLBAR_REG; if (ftoregister & AFX_WNDMDIFRAME_REG) // MDI Frame window (also used for splitter window) wndcls.style = CS_DBLCLKS; wndcls.hbrbackground = NULL; if (_AfxRegisterWithIcon(&wndcls, _afxwndmdiframe, AFX_IDI_STD_MDIFRAME)) fregisteredclasses = AFX_WNDMDIFRAME_REG; if (ftoregister & AFX_WNDFRAMEORVIEW_REG) // SDI Frame or MDI Child windows or views - normal colors wndcls.style = CS_DBLCLKS CS_HREDRAW CS_VREDRAW; wndcls.hbrbackground = (HBRUSH) (COLOR_WINDOW + 1); if (_AfxRegisterWithIcon(&wndcls, _afxwndframeorview, AFX_IDI_STD_FRAME))

fregisteredclasses = AFX_WNDFRAMEORVIEW_REG; if (ftoregister & AFX_WNDCOMMCTLS_REG) // this flag is compatible with the old InitCommonControls() API init.dwicc = ICC_WIN95_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WIN95CTLS_MASK); ftoregister &= ~AFX_WIN95CTLS_MASK; if (ftoregister & AFX_WNDCOMMCTL_UPDOWN_REG) init.dwicc = ICC_UPDOWN_CLASS; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_UPDOWN_REG); if (ftoregister & AFX_WNDCOMMCTL_TREEVIEW_REG) init.dwicc = ICC_TREEVIEW_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TREEVIEW_REG); if (ftoregister & AFX_WNDCOMMCTL_TAB_REG) init.dwicc = ICC_TAB_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TAB_REG); if (ftoregister & AFX_WNDCOMMCTL_PROGRESS_REG) init.dwicc = ICC_PROGRESS_CLASS; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PROGRESS_REG); if (ftoregister & AFX_WNDCOMMCTL_LISTVIEW_REG) init.dwicc = ICC_LISTVIEW_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LISTVIEW_REG); if (ftoregister & AFX_WNDCOMMCTL_HOTKEY_REG) init.dwicc = ICC_HOTKEY_CLASS; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_HOTKEY_REG); if (ftoregister & AFX_WNDCOMMCTL_BAR_REG) init.dwicc = ICC_BAR_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_BAR_REG); if (ftoregister & AFX_WNDCOMMCTL_ANIMATE_REG) init.dwicc = ICC_ANIMATE_CLASS; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_ANIMATE_REG);

if (ftoregister & AFX_WNDCOMMCTL_INTERNET_REG) init.dwicc = ICC_INTERNET_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_INTERNET_REG); if (ftoregister & AFX_WNDCOMMCTL_COOL_REG) init.dwicc = ICC_COOL_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_COOL_REG); if (ftoregister & AFX_WNDCOMMCTL_USEREX_REG) init.dwicc = ICC_USEREX_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_USEREX_REG); if (ftoregister & AFX_WNDCOMMCTL_DATE_REG) init.dwicc = ICC_DATE_CLASSES; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_DATE_REG); if (ftoregister & AFX_WNDCOMMCTL_LINK_REG) init.dwicc = ICC_LINK_CLASS; fregisteredclasses = _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LINK_REG); // save new state of registered controls pmodulestate->m_fregisteredclasses = fregisteredclasses; // special case for all common controls registered, turn on AFX_WNDCOMMCTLS_REG if ((pmodulestate->m_fregisteredclasses & AFX_WIN95CTLS_MASK) == AFX_WIN95CTLS_MASK) pmodulestate->m_fregisteredclasses = AFX_WNDCOMMCTLS_REG; fregisteredclasses = AFX_WNDCOMMCTLS_REG; // must have registered at least as mamy classes as requested return (ftoregister & fregisteredclasses) == ftoregister; 首先,AfxEndDeferRegisterClass 会检查参数 ftoregister, 判断目标窗体是否需要注册 这一点很重要, 因 为 MFC 会尝试在多个地方注册窗口类 然再利用 memset 清空 WNDCLASS 的结构, 这样一来, 除了那些显示设置的地方, 其他的都是 NULL 然 后我们看看初始化动作

Wndcls::lpfnWndProc 首先被初始化指向 DefWindowProc(),( 如果你用 SDK 写过 Windows 程序, 对这个函 数应该并不陌生 ); 然后将 wndcls::hinstance 初始化为当前实例的句柄 ; 最后设置 wndcls::hcursor 为一般的 鼠标指针 接下来就是相似的动作 : 注册窗口类 它会根据不同的窗口对象类型来注册不同的窗口类, 而且类风格 (Class-Style) 也不尽相同 这里,M$ 决定在窗口创建其间注册窗口类, 而不是在程序开始时注册窗口类, 这不能不说是一个明智的 决定 窗口类注册需要时间, 而 MFC 这样做则可以避免注册那些没有必要的窗口类, 这样就能优化启动过程 9.2. 窗口的 HOOK 在应用程序的初始化过程中,MFC 会安装几个钩子 在 AfxInitThread() 函数中 ( 就是在 AfxWinInit() 中被我们跳过的那个函数 ),MFC 会安装 WH_MSGFILTER 钩 子, 用来监听对话框 消息框 菜单条等控件中的输入事件 然后又在 CWnd::Create 之前, 利用 AfxHookWindowCreate(CWnd* pwnd) 安装了 WH_CBT 钩子 安装这个 钩子是为了让窗体在被创建时立刻进入 MFC 不能等到 CreateWindowEx() 返回的时候在进入, 因为那个时 候已经有一些消息被发送 CWnd 对象必须在任何消息被送给窗口之前让 MFC 进入 WH_CBT 就顺带的完成了这个使命 ( 为什么说 顺带, 因为这个不是 WH_CBT 的主要使命 ) 对于 MFC7.X 来说,MFC 总共安装了 4 个钩子, 除了上面两个, 还有 WH_KEYBOARD 和 WH_MOUSE, 但 是本人不才, 没发现这两个在哪里装的 - -! 知道的麻烦告诉我 ~ 10. CWinApp::Run() MFC 的消息泵 一个合格的 Windows 程序必须要能接受并过滤消息, 并且对于指定的消息, 要能做出相应的反应 在 SDK 中, 我们使用 GetMessage()...DispatchMessage() 循环和回调函数来完成这些功能, 但是在 MFC 中, 会有一点点不同 这里, 我们先回到 AfxWinMain() 函数, 看到了 nreturncode = pthread->run(); 了么? 使用 Run() 会自动启动 消息循环 因为多态的特性, 上面的代码会调用 CWinApp::Run(), 所以我们先看看 CWinApp::Run() 中都有哪些神奇 : int CWinApp::Run() if (m_pmainwnd == NULL && AfxOleGetUserCtrl()) // Not launched /Embedding or /Automation, but has no main window!

TRACE(traceAppMsg, 0, "Warning: m_pmainwnd is NULL in CWinApp::Run - quitting application.\n"); AfxPostQuitMessage(0); return CWinThread::Run(); 明显, 还和 CWinThread::Run() 有关 ( 别忘了前面说到的 MFC7.X 中 Run 实际位于 CWinThread) 既然如 此, 我们还是继续来看 CWinThread::Run()( 源代码在 THRDCORD.CPP 中 ): int CWinThread::Run() ASSERT_VALID(this); _AFX_THREAD_STATE* pstate = AfxGetThreadState(); // for tracking the idle time state BOOL bidle = TRUE; LONG lidlecount = 0; // acquire and dispatch messages until a WM_QUIT message is received. for (;;) // phase1: check to see if we can do idle work while (bidle &&!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE)) // call OnIdle while in bidle state if (!OnIdle(lIdleCount++)) bidle = FALSE; // assume "no idle" state // phase2: pump messages while available do // pump message, but quit on WM_QUIT if (!PumpMessage()) return ExitInstance(); // reset "no idle" state after pumping "normal" message //if (IsIdleMessage(&m_msgCur)) if (IsIdleMessage(&(pState->m_msgCur))) bidle = TRUE; lidlecount = 0; while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));

可以发现重点的函数 PumpMessage(), 继续跟进 ~ 可以发现这个函数的代码如下 : BOOL CWinThread::PumpMessage() return AfxInternalPumpMessage(); PumpMessage() 把处理委托给了 AfxInternalPumpMessage()- -!, 继续跟进吧 ~ BOOL AFXAPI AfxInternalPumpMessage() _AFX_THREAD_STATE *pstate = AfxGetThreadState(); if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)) #ifdef _DEBUG TRACE(traceAppMsg, 1, "CWinThread::PumpMessage - Received WM_QUIT.\n"); pstate->m_ndisablepumpcount++; // application must die #endif // Note: prevents calling message loop things in 'ExitInstance' // will never be decremented return FALSE; #ifdef _DEBUG if (pstate->m_ndisablepumpcount!= 0) TRACE(traceAppMsg, 0, "Error: CWinThread::PumpMessage called when not permitted.\n"); ASSERT(FALSE); #endif #ifdef _DEBUG _AfxTraceMsg(_T("PumpMessage"), &(pstate->m_msgcur)); #endif // process this message if (pstate->m_msgcur.message!= WM_KICKIDLE &&!AfxPreTranslateMessage(&(pState->m_msgCur))) ::TranslateMessage(&(pState->m_msgCur)); ::DispatchMessage(&(pState->m_msgCur)); return TRUE; 终于, 我们发现了熟悉的东西!

MFC 仍然使用 GetMessage() 获取消息, 然后 DispatchMessage() 给处理函数 还记得前面注册窗口类的时 候, 用 AfxEndDeferRegisterClass 把回调函数指定为 DefWindowProc() 么? 不过实际上, 消息并不是送往 DefWindowProc(), 而是被转送到 AfxWndProc() 全局函数中 ( 其中使用了大 量的 Hook 的 Subclass) 因为 MFC 要通过消息映射机制来处理消息 11. 消息映射机制 看到这个标题别害怕, 我们现在不研究 MFC 的消息运行机制 因为 MFC 的消息运行机制比 Windows 的 消息运行机制更加复杂更加扑朔迷离 当然, 这也更加有趣 ~ 我们现在真是轻松的浏览下, 对 MFC 的消息映射机制大概有个了解 我们在开始例子代码中 CMFCAppWinow 中写下了消息映射的声明 : class CMFCAppWindow : public CFrameWnd // 下面是消息映射的东东 afx_msg void OnLButtonDblClk(UINT nflags, CPoint point); // 左键双击的消息声明 afx_msg void OnPaint(); // WM_PAINT 消息声明 DECLARE_MESSAGE_MAP() // 消息映射宏 ; 然后指定消息映射表 : // MS 叫做消息映射表 BEGIN_MESSAGE_MAP(CMFCAppWindow, CFrameWnd) ON_WM_LBUTTONDBLCLK() ON_WM_PAINT() END_MESSAGE_MAP() 最后在消息映射函数中作出相应的动作 12. 尾声 好了, 终于要结尾了 该讲的我都已经讲完了 ~ 大家也应该对 MFC 程序的基本运行机制有了一定的理解 ~ 然后要说的是, 研究 MFC 一定要自己勤动手, 勤查 MSDN M$ 知识库和 Google 最重要的就是勤翻 MFC 的源代码, 因为 MFC 改版很快, 而源代码能让你对 MFC 的行为心里有数 如果你以前看过 MFC 程序的内 幕, 可能会发现很多地方和文章里面讲的有出入 是的, 这就像开头说的,MFC 并不完美, 也永远不可能完美, 因为我们都在进步!

还有就是, 本人也是刚接触 MFC 不久, 因为水平有限, 对 MFC 的运行机制也只能做到基本剖析 如果 文章中存在问题, 请告诉我, 谢谢! 最后感谢一直支持我的众多 CFAN 编程版 MDSA Group 和 JAFT 内阁委的兄弟姐妹 ~

A. 附录 SetErrorMode The SetErrorMode function controls whether the system will handle the specified types of serious errors, or whether the process will handle them. UINT SetErrorMode( UINT umode ); Parameters umode [in] Process error mode. This parameter can be one or more of the following values. Value Meaning 0 Use the system default, which is to display all error dialog boxes. SEM_FAILCRITICALERRORS SEM_NOALIGNMENTFAULTEXCEPT SEM_NOGPFAULTERRORBOX SEM_NOOPENFILEERRORBOX The system does not display the critical-error-handler message box. Instead, the system sends the error to the calling process. 64-bit Windows: The system automatically fixes memory alignment faults and makes them invisible to the application. It does this for the calling process and any descendant processes. After this value is set for a process, subsequent attempts to clear the value are ignored. The system does not display the general-protection-fault message box. This flag should only be set by debugging applications that handle general protection (GP) faults themselves with an exception handler. The system does not display a message box when it fails to find a file. Instead, the error is returned to the calling process. Return Values The return value is the previous state of the error-mode bit flags. Remarks Each process has an associated error mode that indicates to the system how the application is going to respond to serious errors. A child process inherits the error mode of its parent process. Itanium: An application must explicitly call SetErrorMode with SEM_NOALIGNMENTFAULTEXCEPT to have the system automatically fix alignment faults. The default setting is for the system to make alignment faults visible to an application x86: The system does not make alignment faults visible to an application. Therefore, specifying SEM_NOALIGNMENTFAULTEXCEPT is not an error, but the system is free to silently ignore the request. This means that code sequences such as the following are not always valid on x86 computers:

SetErrorMode(SEM_NOALIGNMENTFAULTEXCEPT); fuolderrormode = SetErrorMode(0); ASSERT(fuOldErrorMode == SEM_NOALIGNMENTFAULTEXCEPT); RISC: Misaligned memory references cause an alignment fault exception. To control whether the system automatically fixes such alignment faults or makes them visible to an application, use SEM_NOALIGNMENTFAULTEXCEPT. MIPS: An application must explicitly call SetErrorMode with SEM_NOALIGNMENTFAULTEXCEPT to have the system automatically fix alignment faults. The default setting is for the system to make alignment faults visible to an application. Alpha: To control the alignment fault behavior, set the EnableAlignmentFaultExceptions value in the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager registry key as follows. Value Meaning 0 Automatically fix alignment faults. This is the default. 1 Make alignment faults visible to the application. You must call SetErrorMode with SEM_NOALIGNMENTFAULTEXCEPT to have the system automatically fix alignment faults. Requirements Client: Included in Windows XP, Windows 2000 Professional, Windows NT Workstation, Windows Me, Windows 98, and Windows 95. Server: Included in Windows Server 2003, Windows 2000 Server, and Windows NT Server. Header: Declared in Winbase.h; include Windows.h. Library: Use Kernel32.lib. GetModuleFileName The GetModuleFileName function retrieves the fully qualified path for the specified module. To specify the process that contains the module, use the GetModuleFileNameEx function. DWORD GetModuleFileName( HMODULE hmodule, LPTSTR lpfilename, DWORD nsize ); Parameters hmodule [in] Handle to the module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves the path for the current module. lpfilename [out] Pointer to a buffer that receives a null-terminated string that specifies the fully-qualified path of the module. If the length of the path exceeds the size specified by the nsize parameter, the function succeeds and the string is truncated to nsize characters and null terminated. The path can have the prefix "\\?\", depending on how the module was loaded. For more information, see Naming a File. nsize [in] Size of the lpfilename buffer, in TCHARs. Return Values

If the function succeeds, the return value is the length of the string copied to the buffer, in TCHARs. If the buffer is too small to hold the module name, the string is truncated to nsize, and the function returns nsize. If the function fails, the return value is zero. To get extended error information, call GetLastError. Remarks If a DLL is loaded in two processes, its file name in one process may differ in case from its file name in the other process. For the ANSI version of the function, the number of TCHARs is the number of bytes; for the Unicode version, it is the number of characters. Windows Me/98/95: This function retrieves long file names when an application's version number is greater than or equal to 4.00 and the long file name is available. Otherwise, it returns only 8.3 format file names. Windows Me/98/95: GetModuleFileNameW is supported by the Microsoft Layer for Unicode. To use this, you must add certain files to your application, as outlined in Microsoft Layer for Unicode on Windows Me/98/95 Systems. Requirements Client: Included in Windows XP, Windows 2000 Professional, Windows NT Workstation, Windows Me, Windows 98, and Windows 95. Server: Included in Windows Server 2003, Windows 2000 Server, and Windows NT Server. Unicode: Implemented as Unicode and ANSI versions. Note that Unicode support on Windows Me/98/95 requires Microsoft Layer for Unicode. Header: Declared in Winbase.h; include Windows.h. Library: Use Kernel32.lib. AfxThrowUserException Throws an exception to stop an end-user operation. void AfxThrowUserException( ); Remarks This function is normally called immediately after AfxMessageBox has reported an error to the user. PathFindExtension Searches a path for an extension. Syntax LPTSTR PathFindExtension( LPCTSTR ppath ); Parameters ppath [in] Pointer to a null-terminated string of maximum length MAX_PATH that contains the path that contains the extension for which to search. Return Value Returns the address of the "." preceding the extension within ppath if an extension is found, or the address of the trailing NULL character otherwise. Function Information

Minimum DLL Version shlwapi.dll version 4.71 or later Custom Implementation No Header Import library Minimum operating systems shlwapi.h shlwapi.lib Windows 2000, Windows NT 4.0 with Internet Explorer 4.0, Windows 98, Windows 95 with Internet Explorer 4.0 AfxEnableMemoryTracking Diagnostic memory tracking is normally enabled in the Debug version of MFC. BOOL AfxEnableMemoryTracking( BOOL btrack ); Parameters btrack Setting this value to TRUE turns on memory tracking; FALSE turns it off. Return Value The previous setting of the tracking-enable flag. Remarks Use this function to disable tracking on sections of your code that you know are allocating blocks correctly. For more information on AfxEnableMemoryTracking, see Debugging MFC Applications. Note This function works only in the Debug version of MFC. CFrameWnd::Create Call to create and initialize the Windows frame window associated with the CFrameWnd object. virtual 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 ); Parameters lpszclassname Points to a null-terminated character string that names the Windows class. The class name can be any name registered with the AfxRegisterWndClass global function or the RegisterClass Windows function. If NULL, uses the predefined default CFrameWnd attributes. lpszwindowname Points to a null-terminated character string that represents the window name. Used as text for the title bar.

dwstyle Specifies the window style attributes. Include the FWS_ADDTOTITLE style if you want the title bar to automatically display the name of the document represented in the window. rect Specifies the size and position of the window. The rectdefault value allows Windows to specify the size and position of the new window. pparentwnd Specifies the parent window of this frame window. This parameter should be NULL for top-level frame windows. lpszmenuname Identifies the name of the menu resource to be used with the window. Use MAKEINTRESOURCE if the menu has an integer ID instead of a string. This parameter can be NULL. dwexstyle Specifies the window extended style attributes. pcontext Specifies a pointer to a CCreateContext structure. This parameter can be NULL. Return Value Nonzero if initialization is successful; otherwise 0. Remarks Construct a CFrameWnd object in two steps. First, invoke the constructor, which constructs the CFrameWnd object, and then call Create, which creates the Windows frame window and attaches it to the CFrameWnd object. Create initializes the window's class name and window name and registers default values for its style, parent, and associated menu. Use LoadFrame rather than Create to load the frame window from a resource instead of specifying its arguments. B. 参考文献 Microsoft MSDN http://msdn2.microsoft.com/zh-cn/default.aspx Microsoft Support http://support.microsoft.com/gp/kbindex Inside the Microsoft Foundation Class Architecture Dissecting MFC 2e C. 版权声明 Copyright KingsamChen [MDSA Group] My Blog:http://kingsamchen.blogspot.com MSN:kingsamchen@hotmail.com QQ:106047259

E-mail:kingsamchen@hotmail.com / kingsamchen@gmail.com MSDA BBS:http://mdsa-group.5d6d.com CFAN BBS:http://bbs.cfan.com.cn