一.從windows程序開始,以消息爲基礎,以事件驅動之的win32程序;c++
windows程序依靠外部事件驅動來運行,操做系統捕捉消息,放入應用程序的消息隊列,應用程序從消息隊列中取出消息,針對不一樣的消息採起不一樣的行爲做爲響應。應用程序要完成某些功能,都是以函數調用形式實現,以函數調用通知操做系統來完成指定的功能,如輸入輸出。操做系統所能完成的每個功能都有一個特殊的函數與之對應。操做系統把它所能完成的功能封裝成函數供操做系統調用,這些函數的集合就是windows操做系統提供給應用程序的編程接口,就是windows api函數。編程
具體到windows程序,消息是一個名爲MSG的數據結構,封裝了一個windows消息的各類屬性。接受消息的主角是應用程序窗口,每個窗口都應該有一個對應的窗口處理函數,負責處理由窗口接收的不一樣消息。windows
盜了一幅圖來講明windows程序的消息驅動機制:api
圖1.windows應用程序消息驅動機制數據結構
消息的結構:併發
1 typedef struct { 2 HWND hwnd; /*Handle to the window whose window procedure receives the message. 3 hwnd is NULL when the message is a thread message. */ 4 UINT message; /*Specifies the message identifier. Applications can only use the 5 low word; the high word is reserved by the system*/ 6 WPARAM wParam; /*Specifies additional information about the message. The exact 7 meaning depends on the value of the message member*/ 8 LPARAM lParam; /*Specifies additional information about the message. The exact 9 meaning depends on the value of the message member*/ 10 DWORD time; /*Specifies the time at which the message was posted. */ 11 POINT pt; /*Specifies the cursor position, in screen coordinates, when the message was posted. */ 12 } MSG, *PMSG;
能夠用一個簡單的程序來模擬windows的消息機制:app
1 #include <windows.h> 2 #include "resource.h" 3 #include "generic.h" 4 5 HINSTANCE _hInst; 6 HWND _hWnd; 7 8 char _szAppName[] = "Generic"; 9 char _szTitle[] ="Generic Sample Application"; 10 11 /* 12 *The WinMain function is the conventional name for the user-provided entry point 13 *for a Windows-based application. 14 *@@Param: 15 *@HINSTANCE hInstance :Handle to the current instance of the application. 16 * 17 *@HINSTANCE hPrevInstance:Handle to the previous instance of the application. This 18 *parameter is always NULL. 19 *If you need to detect whether another instance already exists, create a uniquely 20 *named mutex using the CreateMutex function. CreateMutex will succeed even if the 21 *mutex already exists, but the function will return ERROR_ALREADY_EXISTS. 22 *This indicates that another instance of your application exists, because it created 23 *the mutex first. However, a malicious user can create this mutex before you do and 24 *prevent your application from starting. To prevent this situation, create a randomly 25 *named mutex and store the name so that it can only be obtained by an authorized user. 26 *Alternatively, you can use a file for this purpose. To limit your application to one 27 *instance per user, create a locked file in the user's profile directory. 28 * 29 *@LPSTR lpCmdLine:Pointer to a null-terminated string specifying the command line for 30 *the application, excluding the program name. To retrieve the entire command line, use 31 *the GetCommandLine function. 32 * 33 *@int nCmdShow:Specifies how the window is to be shown. This parameter can be one of the following values. 34 *SW_HIDE: Hides the window and activates another window. 35 *SW_MAXIMIZE: Maximizes the specified window. 36 *SW_MINIMIZE: Minimizes the specified window and activates the next top-level window in the Z order. 37 *SW_RESTORE: Activates and displays the window. If the window is minimized or maximized, the system 38 * restores it to its original size and position. An application should specify this flag 39 * when restoring a minimized window. 40 *SW_SHOW: Activates the window and displays it in its current size and position. 41 *SW_SHOWMAXIMIZED:Activates the window and displays it as a maximized window. 42 *SW_SHOWMINIMIZED:Activates the window and displays it as a minimized window. 43 *SW_SHOWMINNOACTIVE:Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, 44 * except the window is not activated. 45 *SW_SHOWNA: Displays the window in its current size and position. This value is similar to SW_SHOW, 46 * except the window is not activated. 47 *SW_SHOWNOACTIVATE:Displays a window in its most recent size and position. This value is similar to 48 * SW_SHOWNORMAL, except the window is not actived. 49 *SW_SHOWNORMAL:Activates and displays a window. If the window is minimized or maximized, the system 50 * restores it to its original size and position. An application should specify this flag 51 * when displaying the window for the first time. 52 */ 53 int CALLBACK winMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){ 54 MSG msg; 55 56 UNREFERENCED_PARAMETER(lpCmdLine); 57 if(!hPrevInstance)//若是沒有父實例進程,初始化應用:定義窗口,並註冊。只執行一次 58 if(!InitApplication(hInstance)) 59 return (FALSE); 60 61 if(!InitInstance(hInstance,nCmdShow))//爲每一實例建立建立窗口; 62 return (FALSE); 63 64 while(GetMessage(&msg,NLL,0,0)){//開啓消息循環 65 TranslateMessage(&msg); 66 DispatchMessage(&msg); 67 } 68 69 return (msg.wParam); 70 } 71 /* 72 InitApplication函數中定義一個窗口,其屬性與行爲,並完成註冊; 73 一旦定義並註冊好一個窗口,能夠被以後多個應用程序實例使用; 74 此處提供了一個窗口模板; 75 */ 76 BOOL InitApplication(HINSTANCE hInstance) 77 { 78 //定義一個窗口和其屬性行爲 79 WNDCLASS wc; 80 wc.style = CS_HREADAW | CS_VERDAW; 81 wc.lpfnWnProc = (WNDPROC)WndProc;//此屬性爲窗口對應的窗口函數,賦值爲一個函數指針 82 wc.cbClsExtra = 0; 83 wc.cbWndExtra = 0; 84 wc.hInstance = hInstance;//實例句柄 85 wc.hIcon = LaodIcon(hInstance,"XXXX"); 86 wc.hbrBackground = GetStockObject(WHITE_BRUSH); 87 wc.lpszMenuName = "GenericMenu"; 88 wc.lpzeClassName = _szAppName; 89 return (RegisterClass(&wc)); 90 } 91 /* 92 Initstance函數中完成窗口的建立,顯示,與更新; 93 每一個應用程序實例都應該調用此函數建立本身的窗口; 94 */ 95 BOOL InitInstance(HINSTANCE hInstance,int nCmdShow) 96 { 97 _hInst = hInstance; 98 _hWnd = CreateWindow( 99 _szAppName, 100 _szTitle, 101 WS_OVERLAPPEDWINDOW, 102 CW_USEDEFAULT, 103 CW_USEDEFAULT, 104 CW_USEDEFAULT, 105 CW_USEDEFAULT, 106 NULL, 107 NULL, 108 hInstance, 109 NULL 110 ); 111 if(!_hWnd) 112 return FALSE; 113 ShowWindow(_hWnd,nCmdShow); 114 UpdateWindow(_hWnd); 115 return TRUE; 116 } 117 /* 118 *窗口函數:處理有窗口接收到的消息,窗口的行爲中樞; 119 *@@Param: 120 *@HWND hWnd: Handle to the window. 121 *@UINT message: Specifies the message. 122 *@WPARAM wParam:Specifies additional message information. The contents of this 123 * parameter depend on the value of the uMsg parameter; 124 * wParam一般是與消息有關的一個常量值,也多是窗口或控件的句柄; 125 *@LPARAM lParam:Specifies additional message information. The contents of this 126 * parameter depend on the value of the uMsg parameter. 127 * lParam一般是一個指向內存中數據的指針; 128 */ 129 LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) 130 { 131 int wmId,wmEvent; 132 switch (message){ 133 case WM_COMMAND: 134 wmId = LOWORD(wParam); 135 wmEvent = HIWORD(wParam); 136 137 switch(wmId){ 138 case ID_ABOUT: 139 DialogBox(_hInst,"AboutBox",hWnd),(DLGPROC)About); 140 break; 141 case IDM_EXIT: 142 DestroyWindow(hWnd); 143 break; 144 default: 145 return (DefWindowProc(hWnd,message,wParam,lParam)); 146 } 147 break; 148 case WM_DESTROY: 149 PostQuitMessage(0); 150 break; 151 default: 152 return (DefWindowProc(hWnd,message,wParam,lParam)); 153 } 154 return 0; 155 }
winMain函數在定義好了窗口模板,並完成註冊,在InitInstance函數中爲應用建立好了窗口,接下來就萬事俱備,等待消息驅動,程序也進入了一個while循環,即消息循環: GetMessage函數從消息隊列中抓取消息,判斷是否爲個人消息,若是不是將釋放CPU控制權,讓其餘程序運行,以達到多任務協調運行。若是是本應用的消息,進入while循環體,TranslateMessage將鍵盤消息轉化,DispatchMessage分發消息給窗口處理函數,此處只有一個消息結構體做爲參數,並未指定分發方向,可是,每個消息體第一個成員就指定了接收他的窗口(這個參數的賦值由操做系統完成),而每個窗口必有惟一指定的窗口處理函數。窗口處理函數WndProc是一個CALLBACK函數,由操做系統調用;框架
以上WndProc函數對不一樣消息的處理是由Switch/case實現,這樣的結構一般能夠重構成更精簡通用的形式。暫且先弄明白win32程序的整個生命週期;dom
圖2:win32程序生命週期ide
應用程序InitInstance初始化過程當中,調用CreateWindow函數,CreateWindow函數根據當前實例對應的窗口模板建立窗口,但並不顯示窗口,併產生WM_CREATE消息,該消息直接發送給窗口函數而不通過消息隊列;窗口函數能夠在捕獲此消息時知道窗口已建立,程序準備開始,所以也能夠作自定義一些初始化工做;
ShowWindow和UpdateWindow函數完成窗口的顯示。接着整個程序進入消息循環,接收消息驅動;
GetMessage負責從消息隊列中抓取消息,若是消息不是WM_QUIT,就繼續循環,一旦抓取到WM_QUIT,GetMessage將返回0,while循環結束;
DispatchMessage分發消息給窗口處理函數;
當用戶出發窗口關閉按鈕,操做系統將向消息隊列寫入WM_CLOSE消息,自定義的窗口處理函數通常不處理此類消息,所以DefWindowProc函數將負責處理全部這類消息。DefWindow接收到WM_CLOSE消息後,將調用系統的DestoryWindow API函數清除窗口,併發送WM_DESTORY消息。自定義窗口函數中對WM_DESTORY消息處理通常都是調用PostQuitMessage(0),此函數接着產生WM_QUIT消息。
重構Switch/Case形式的消息處理:
能夠定義這樣一種映射,包含全部的消息類型和該消息的處理例程,沒接收到消息,僅僅是檢索這個映射找到相應的消息處理例程,這個映射是獨立於消息處理函數,所以能夠隨意的添加或刪除消息映射項。換一種方式說,提供給窗口處理函數一個消息路由表,讓窗口處理函數按消息路由表辦事,窗口處理函數並不參與此路由表的建立於編輯。所以程序相對靈活;甚至,路由能夠組織成多級路由表,使消息映射的查找更爲敏捷;
定義這樣的結構體和宏:
1 /*消息路由表項*/ 2 struct MSGMAP_ENTRY { 3 UINT nMessage; 4 LONG (*pfn)(HWND,UINT,WPARAM,LPARAM); 5 }; 6 /*取得路由表大小*/ 7 #define dim(x) (szieof(x) / sizeof(x[0])); 8 9 /*建立路由表*/ 10 struct MSGMAP_ENTRY _messageEntries[]= 11 { 12 /* data */ 13 WM_CREATE,OnCreate, 14 WM_PAINT,OnPaint, 15 WM_SIZE,OnSize, 16 WM_COMMAND,OnCommand, 17 WM_SETFOCUS,OnSetFocus, 18 WM_CLOSE,OnClose, 19 WM_DESTROY,OnDestroy, 20 }; 21 /*命令路由子表*/ 22 struct MSGMAP_ENTRY _commandEntries[]= 23 { 24 /* data */ 25 IDM_ABOUT,OnAbout, 26 IDM_FILEOPEN,OnFileOpen, 27 IDM_SAVES,OnSaveAs, 28 };
窗口處理函數能夠重構爲:
1 LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) 2 { 3 int i = 0; 4 //簡單檢索路由表,找到相應的處理例程 5 for(i = 0;i<dim(_messageEntries);i++){ 6 if(message == _messageEntries[i].nMessage) 7 return ((*_messageEntries[i].pfn)(hwnd,message,wParam,lParam)); 8 } 9 return (DefWindowProc(hwnd,message,wParam,lParam)); 10 } 11 12 //WM_COMMAND命令交給OnCommand函數例程,OnCommand將繼續查詢路由子表 13 LONG OnCommand(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) 14 { 15 int i = 0; 16 for(i = 0;i<dim(_commandEntries);i++){ 17 if(LOWORD(wParam) == _commandEntries[i].nMessage) 18 return ((*_commandEntries[i].pfn)(hwnd,message,wParam,lParam)); 19 } 20 return (DefWindowProc(hwnd,message,wParam,lParam)); 21 } 22 23 LONG OnCreate(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){} 24 LONG OnSize(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){} 25 //...
程序空閒時怎麼辦?
多任務系統中常常是併發多個程序,有很大機率在很長一段時間內某一應用程序在消息對列中並無屬於他的消息,此時GetMessage若是未能從消息隊列中抓取消息,程序的主執行線程(注意這個詞:主執行線程)會被操做系統虛懸,讓出CPU控制權,轉而執行其餘程序。一段時間後,應用程序再次得到CPU,若是此時仍然沒有消息,GetMessage繼續讓出CPU。
PeekMessage與GetMessag負責相同的任務,但具備不一樣的表現,主要體如今,當程序從新得到CPU,PeekMessage沒法得到此應用的消息時,並不當即讓出CPU,而是進入OnIdle函數運行空閒是程序任務;使用PeekMessage的消息循環:
1 while(TRUE){ 2 if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){ 3 if(msg.message == WM_QUIT) 4 break; 5 TranslateMessage(&msg); 6 DispatchMessage(&msg); 7 } 8 else{ 9 OnIdle(); 10 } 11 }
二.MFC過渡,一個簡潔的類MFC程序
MFC程序也是Windows程序,也必須遵循windows程序的機制,MFC只是在windows程序的標準之上,封裝了一些經常使用操做,重構爲一些類和接口函數,方便開發和調用。MFC稱爲一套Application Framework。略去一些必要的頭文件,從剖析一個簡單的MFC程序縮影開始整理MFC邏輯。
1 /*HELLO.H*/ 2 class CMyWinApp:public CMyWinApp 3 { 4 public: 5 BOOL InitInstance(); 6 }; 7 8 class CMyFrameWnd:public CFrameWnd 9 { 10 public: 11 CMyFrameWnd(); 12 afx_msg void OnPaint(); 13 afx_msg void OnAbout(); 14 15 private: 16 DECLARE_MESSAGE_MAP() 17 static VOID CALLBACK someoperate(); 18 } 19 20 /*HELLO.CPP*/ 21 #include <afxwin.h> 22 #include <hello.h> 23 24 CMyWinApp theApp; 25 26 BOOL CMyWinApp::InitInstance() 27 { 28 m_pMainWnd = new CMyFrameWnd(); 29 m_pMainWnd->ShowWindow(m_nCmdShow); 30 m_pMainWnd->UpdateWindow(); 31 return TRUE; 32 } 33 CMyFrameWnd::CMyFrameWnd() 34 { 35 Create(NULL,"Hello,MFC",WS_OVERLAPPEDWINDOW,rectDedault,NULL,"MainMenu"); 36 } 37 38 BEGIN_MESSAGE_MAP(CMyFrameWnd,CFrameWnd) 39 ON_COMMAND(IDM_ABOUT,OnAbout) 40 ON_WM_PAINT() 41 END_MESSAGE_MAP() 42 43 void CMyFrameWnd::OnPaint() 44 { 45 /**/ 46 }
這是一個可運行的「hello world 」MFC程序的主框架,修剪掉一些控件和具體消息處理例程,就只用到了兩個主要類,CWinApp和CFrameWnd,以及他們的衍生類。程序自己已經包含了Windows程序的生命週期函數,但僅有以上兩個類還沒法窺得MFC程序的執行流程,是由於MFC即成爲一個優秀的application framew,對windows程序進行了重構,使其面向對象,面向接口調用。
從哪裏開始?
首先,程序沒有如win32程序那樣的有一個winMain函數做爲入口點,但一個可執行程序一定會有一個入口函數接收標準輸入參數以啓動整個程序,在HELLO.CPP文件中,在大多數類函數外,實例化了一個CMyWinApp類對象theApp,這個對象做爲全局對象,在任何函數被調用前被建立,將調用類構造函數;CMyWinApp繼承自CWinApp,MFC中稱CWinApp的衍生對象爲application object,表明了一個應用程序實例(這句話要好好體會),CWinApp在頭文件AFXWIN.H中定義,從其節選部分代碼:
1 class CWinApp : public CWinThread 2 { 3 //Attributes windows程序中由操做系統傳遞給winMain函數的四個參數 4 HINSTANCE m_hInstance; 5 HINSTANCE m_PrevInstance; 6 LPSTR m_lpCmdLine; 7 int m_nCmdShow; 8 9 //Runing args 10 LPCTSTR m_pszAppName; //human readable name 11 LPCTSTR m_pszRegistryKey;//userd for registry entries 12 13 public: 14 LPCTSTR m_pszExeName; 15 LPCTSTR m_pszHelpFilePath; 16 LPCTSTR m_pszProfileName; 17 18 public: 19 virtual BOOL InitApplication(); 20 21 virtual BOOL InitInstance(); 22 virtual int ExitInstance(); 23 virtual int Run(); 24 virtual BOOL OnIdle(); 25 }
CWinApp類中定義了四個成員變量,正是windows程序中有操做系統傳遞給winMain函數四個參數,而且,winMain函數通常應該調用的InitApplication,InitInstance也是CWinApp的成員函數,並都提供成虛擬函數,以供衍生類複寫。能夠說CWinApp完成了winMain函數所要完成的任務,InitApplication中定義窗口模板並註冊,IintInstance函數中建立窗口;一切從theApp對象的建立開始,調用類構造函數,構造函數中完成一些線程初始化等操做,以後對象被建立;
CWinApp構造函數僅僅是建立了表明應用程序的CWinApp對象,並無調用InitApplication或InitInstance函數初始化和建立窗口,那麼這些必要的起始任務在哪裏完成?可是,做爲應用程序的化身,從CWinApp繼承而來的CMyWinApp中已經複寫好了InitApplication和InitInstance動做,而且第一步已經建立獲得了CMyWinApp全局對象,如今只等對象調用這兩個動做就可完成全部的應用建立任務。
什麼時候調用?
做爲application object對象的theApp配置完成後,MFC程序的」winMain函數「函數開始發揮做用,這個所謂的winMain函數並不是由咱們所寫,而是在MFC中已經定義好了,MFC遵循了一套標準,程序建立一定調用InitApplication和InitInstance,因此MFC已經提早寫好了Main函數:
1 //精簡後 2 int AFXAPI AfxWinMain (HINSTANCE hInstance,HINSTANCE hPrevInstance,LPCTSTR lpCmdLine,LPCTSTR nCmdShow) 3 { 4 int nReturnCode = -1; 5 CWinApp* pApp = AfxGetApp(); 6 7 AfxWinInit(hInstance,hPrevInstance,lpCmdLine,nCmdShow); 8 9 pApp->InitApplication(); 10 pApp->Initstance(); 11 nReturnCode = pApp->Run(); 12 13 AfxWinTerm(); 14 return nReturnCode; 15 }
Afx開頭的函數都是MFC定義的全局函數,不屬於任何類,AfxGetApp函數獲取一開始建立的application object對象theApp,AfxWinInit完成內部初始化,應該與系統有關,先跳過。接着調用了theApp對象的兩個成員函數建立應用,再就調用Run函數展開消息循環。
通常不復寫InitApplication,讓程序調用CWinApp中由MFC定義的InitApplication,這裏完成一些內部管理初始化操做(關於Document等)。
有一個問題:想一想windows程序中InitApplication和InitInstance函數各完成了什麼任務?
Windows程序中winMain函數調用InitApplication函數完成了窗口模板的定義,並註冊,InitInstance函數中以窗口模板建立了窗口,並綁定了窗口函數,最後顯示窗口,等待消息循環,應用程序正式登上歷史舞臺。MFC程序中這兩個函數是否也是如此?
有一點點不一樣,MFC做爲一個完整的application framework,有複雜完整的結構(暫時這麼以爲),在InitApplication函數中並無建立或註冊WNDCLASS的窗口模板,全部這些操做彷佛都在InitInstance中完成。事實上咱們無需定義窗口模板,MFC會爲咱們配置一個標準模板,並註冊,咱們要作的也許只是建立這樣的窗口時添加一些「個性化」的渲染,以使其「不同凡響」;這些操做在哪裏完成,InitInstance中嗎?其實還有一個類CFrameWnd,看一下CMyWinAp::InitInstance函數,這是一個複寫的虛函數,能夠自定義初始化窗口過程,在HELLO.APP中能夠看到,繼續盜圖來講明過程:
CFrameWnd相似乎是窗口類,集窗口的建立,顯示,接收處理窗口函數爲一體,用MFC中的話,「CFrameWnd主要用來掌握一個窗口」,CFrameWnd又是怎樣登上舞臺的?
由CWinAapp全局對象的建立和AfxWinMain函數調用,已經爲程序搭好了後臺環境,而且theApp對象也爲應用「圈好了地」(準備好了進程,分配了內存),就等着應用程序真正運行起來有所做爲,還缺什麼,至少缺兩個主體,雖然有了程序運行的fundation,但windows程序是以消息驅動爲生命的,必須至少有一個接收消息(系統消息和用戶消息)的主體,和一個處理消息的主體,說CWinApp對象表明了整個應用,那應該有這樣兩個主體的體現;上面有提到CFrameWnd掌握了一個窗口,集窗口的定義顯示與行爲爲一體,那麼CWinApp只需體現CFrameWnd就能夠,確實如此,CWinApp有一個成員變量,就是用來描述CFrameWnd,InitInstance函數中new了一個CFrameWnd對象,並把其賦值爲CWinApp的m_pMainWnd成員變量,隨着CFrameWnd對象的誕生,應用程序小世界又發生了巨大的變化。
CFrameWnd構造了什麼?
CFrameWnd繼承自CWnd,程序中定義了一個衍生類繼承CFrameWnd,構造函數中調用了Create方法,衍生類CMyFrameWnd並無定義Create方法,Create方法是CFrameWnd的一個成員函數(非虛,並不是繼承自CWnd),共八個參數,函數原型以下:
1 BOOL CFrameWnd::Create(LPCTSTR lpszClassName, 2 LPCTSTR lpszWindowName, 3 DWORD dwStyle, 4 const RECT& rect, 5 CWnd* pParentWnd, 6 LPCTSTR lpszMenuName, 7 DWORD dwExStyle, 8 CCreateContext* pContext) 9 { 10 HMENU hMenu = NULL; 11 if (lpszMenuName != NULL) 12 { 13 // load in a menu that will get destroyed when window gets destroyed 14 HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU); 15 if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL) 16 { 17 TRACE0("Warning: failed to load menu for CFrameWnd.\n"); 18 PostNcDestroy(); // perhaps delete the C++ object 19 return FALSE; 20 } 21 } 22 23 m_strTitle = lpszWindowName; // save title for later 24 25 if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, 26 rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 27 pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext)) 28 { 29 TRACE0("Warning: failed to create CFrameWnd.\n"); 30 if (hMenu != NULL) 31 DestroyMenu(hMenu); 32 return FALSE; 33 } 34 35 return TRUE; 36 }
Create函數的八個參數:
///////////////////////////////////////////////////////////
Create函數旨在建立一個窗口,但從windows api程序中推斷,窗口的產生過程應是先定義窗口模板,註冊窗口,最後建立,顯示。此處Create要建立窗口,亦不能無中生有,建立以前一定也需定義註冊窗口類別。
Create函數中調用CreateEx函數來完成其主要功能,CFrameWnd及其衍生類中並無從新定義CreateEx函數,因此此處調用的是父類CWnd::CreateEx函數。函數原型以下:
1 BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, 2 LPCTSTR lpszWindowName, DWORD dwStyle, 3 int x, int y, int nWidth, int nHeight, 4 HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam) 5 { 6 // allow modification of several common create parameters 7 CREATESTRUCT cs; 8 cs.dwExStyle = dwExStyle; 9 cs.lpszClass = lpszClassName; 10 cs.lpszName = lpszWindowName; 11 cs.style = dwStyle; 12 cs.x = x; 13 cs.y = y; 14 cs.cx = nWidth; 15 cs.cy = nHeight; 16 cs.hwndParent = hWndParent; 17 cs.hMenu = nIDorHMenu; 18 cs.hInstance = AfxGetInstanceHandle(); 19 cs.lpCreateParams = lpParam; 20 21 if (!PreCreateWindow(cs)) 22 { 23 PostNcDestroy(); 24 return FALSE; 25 } 26 27 AfxHookWindowCreate(this); 28 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass, 29 cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, 30 cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams); 31 32 #ifdef _DEBUG 33 if (hWnd == NULL) 34 { 35 TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X\n", 36 GetLastError()); 37 } 38 #endif 39 40 if (!AfxUnhookWindowCreate()) 41 PostNcDestroy(); // cleanup if CreateWindowEx fails too soon 42 43 if (hWnd == NULL) 44 return FALSE; 45 ASSERT(hWnd == m_hWnd); // should have been set in send msg hook 46 return TRUE; 47 }
CreateEx根據傳入參數初始化了一個CREATESTRUCT對象cs,CREATESTRUCT是一個結構體,在winuser.h頭文件中定義,定義此結構體的目的在於封裝一個窗口應用函數的全部參數,封裝好的「參數結構體」被看成PreCreateWindow的實參傳遞,PreCreateWindow又是何函數?
PreCreateWindow是一個虛函數,CWnd和CFrameWnd中都有實現,調用時基於c++的多態模式。該函數的目的是在窗口建立以前作預處理,函數定義以下:
1 BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs) 2 { 3 if (cs.lpszClass == NULL) 4 { 5 VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); 6 cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background 7 } 8 9 if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4) 10 cs.style |= FWS_PREFIXTITLE; 11 12 if (afxData.bWin4) 13 cs.dwExStyle |= WS_EX_CLIENTEDGE; 14 15 return TRUE; 16 }
參數是一個CREATESTRUCT結構體的一個引用,在函數內能夠修改cs對象的屬性,那意味着函數中能夠修改CWnd::CreateEx函數中對窗口參數信息的預約義,若是自定義的類複寫此函數,將能夠隨意定製窗口的屬性。函數體中AfxDeferRegisterClass是一個定義在AFXIMPL.H中的宏,接受一個AFX_WNDFRAMEORVIEW_REG的窗口類型,檢測其是否被註冊並調用註冊功能,
1 #define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
AfxEndDeferRegisterClass是定義在wincore.cpp中的一個全局函數,完成對指定窗口類別的註冊。從CWnd衍生的各種mfc使用不一樣的窗口類型註冊。當註冊成功,CreateWindowEx建立窗口並返回句柄。
窗口建立完成,showWindow和UpdateWindow顯示更新窗口,最後調用run函數程序開始消息循環,由消息驅動運行起來。
MFC中CFrameWnd的一些類的生命過程函數與此會有點詫異,也許不會再構造函數中調用Create函數,會有一個LoadFrame函數來調用Create,接下來的過程都相同,可類比,LoadFrame完成的更多一些窗口的初始化功能。