一.从windows程序开始,以消息为基础,以事件驱动之的win32程序;
windows程序依靠外部事件驱动来运行,操作系统捕捉消息,放入应用程序的消息队列,应用程序从消息队列中取出消息,针对不同的消息采取不同的行为作为响应。应用程序要完成某些功能,都是以函数调用形式实现,以函数调用通知操作系统来完成指定的功能,如输入输出。操作系统所能完成的每一个功能都有一个特殊的函数与之对应。操作系统把它所能完成的功能封装成函数供操作系统调用,这些函数的集合就是windows操作系统提供给应用程序的编程接口,就是windows api函数。
具体到windows程序,消息是一个名为MSG的数据结构,封装了一个windows消息的各种属性。接受消息的主角是应用程序窗口,每一个窗口都应该有一个对应的窗口处理函数,负责处理由窗口接收的不同消息。
盗了一幅图来说明windows程序的消息驱动机制:
图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的消息机制:
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程序的整个生命周期;
图2:win32程序生命周期
应用程序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. "); 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. "); 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 ", 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完成的更多一些窗口的初始化功能。