小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Win32程序標(biāo)準(zhǔn)開(kāi)發(fā)流程

 昵稱10504424 2013-02-19

一、WinMain入口點(diǎn)

我們?cè)趯W(xué)習(xí)標(biāo)準(zhǔn)C++的時(shí)候,都知道每個(gè)應(yīng)用程序運(yùn)行時(shí)都會(huì)先進(jìn)入入口點(diǎn)函數(shù)main,而當(dāng)從main函數(shù)跳出時(shí)程序就結(jié)束了。在Windows編程里面,也是一樣的,只是我們的入口點(diǎn)函數(shù)不叫main,叫WinMain,這個(gè)函數(shù)不同于main,我們不能亂來(lái),它的定義必須與聲明保持一致。

我建議各位安裝VS的時(shí)候,都順便更新幫助文檔到本地硬盤,這樣我們可以方便查找。有一點(diǎn)要注意,目前DestTop Develop的文檔基本上是英文的,做好心理準(zhǔn)備。

WinMain函數(shù)怎么寫呢,不用記的,到MSDN文檔一搜,直接復(fù)制就行了。


這個(gè)函數(shù)帶了一個(gè)CALLBACK,說(shuō)明它是一個(gè)回調(diào)函數(shù),那么這個(gè)CALLBACK是啥呢。我們先不管,我們先動(dòng)寫一個(gè)Windows,讓大家有一個(gè)更直觀的認(rèn)識(shí)。

1、啟動(dòng)你的開(kāi)發(fā)工具,版本任意。

2、從菜單欄中依次【文件】【新建】【項(xiàng)目】,在新建項(xiàng)目窗口中,選擇Win32-Win32應(yīng)用程序。

2、點(diǎn)擊確定后,會(huì)彈出一個(gè)向?qū)?,單擊【下一步】。?xiàng)目類型選擇Windows應(yīng)用程序,附加選項(xiàng)選擇空項(xiàng)目,我們要自己編寫實(shí)現(xiàn)代碼。

3、單擊完成,項(xiàng)目創(chuàng)建成功。打開(kāi)【解決方案資源管理器】,在“源文件”文件夾上右擊,從菜單中找到【添加】【新建項(xiàng)】,注意,是源文件,不要搞到頭文件去了。

在新建項(xiàng)窗口中選C++代碼文件,.cpp后綴的,不要選錯(cuò)了,選成頭文件,不然無(wú)法編譯,因?yàn)轭^文件是不參與編譯的。文件名隨便。

包含Windows.h頭文件,這個(gè)是最基本的。


然后是入口點(diǎn),這個(gè)我們直接把MSDN的聲明Ctrl + C,然后Ctrl + V上去就行了。


WinMain返回整型,返回0就行了,其實(shí)是進(jìn)程的退出碼,一定要0,不要寫其他,因?yàn)?表示正常退出,其他值表示非正常退出。

剛才我們提到這個(gè)函數(shù)帶了CALLBACK,那么,它是什么?很簡(jiǎn)單,你回到IDE,在CALLBACK上右擊,選【轉(zhuǎn)到定義】,看看吧。

我們看到它其實(shí)是一個(gè)宏,原型如下:

這時(shí)候我們發(fā)現(xiàn)了,它其實(shí)就是__stdcall,那么這個(gè)__stdcall是什么呢?它是和__cdecl關(guān)鍵字對(duì)應(yīng)的,這些資料,你網(wǎng)上搜一下就有了,如果你覺(jué)得不好理解,你不妨這樣認(rèn)為,__stdcall是專門用來(lái)調(diào)用Win API 的,反正MSDN上也是這樣說(shuō)的,它其實(shí)是遵循Pascal的語(yǔ)法調(diào)用標(biāo)準(zhǔn),相對(duì)應(yīng)地,__cdecl是C語(yǔ)言的調(diào)用風(fēng)格,這個(gè)也是編譯器選項(xiàng)。
打開(kāi)項(xiàng)目屬性,找到節(jié)點(diǎn)C/C++\高級(jí),然后查看一下調(diào)用約定,我們看到默認(rèn)是選擇C風(fēng)格調(diào)用的,所以,WIN API 函數(shù)才用上關(guān)鍵字__stdcall,如果你實(shí)在不懂,也沒(méi)關(guān)系,這個(gè)東西一般不影響我們寫代碼,但屬性窗口中的編譯器選項(xiàng)不要亂改,改掉了可能會(huì)導(dǎo)致一些問(wèn)題。

那么CALLBACK有什么特別呢?一句話:函數(shù)不是我們調(diào)用的,但函數(shù)只定義了模型沒(méi)有具體處理,而代碼處理權(quán)在被調(diào)用者手里。怎么說(shuō)呢,我們完全把它理解為.NET中的委托,我想這樣就好理解了,委托只聲明了方法的參數(shù)和返回值,并沒(méi)有具體處理代碼。

WinMain是由系統(tǒng)調(diào)用的,而WinMain中的代碼如何寫,那操作系統(tǒng)就不管了。就好像我告訴你明天有聚會(huì),一起去爬山,反正我是通知你了,至于去不去那是你決定了。

接下來(lái)看看入口點(diǎn)函數(shù)的參數(shù)。

注意,我們平時(shí)看到很多如HANDLE,HINSTANCE,HBRUSH,WPARAM。LPARAM,HICON,HWND等一大串?dāng)?shù)據(jù)類型,也許我們會(huì)說(shuō),怎么Windows開(kāi)發(fā)有那么多數(shù)據(jù)類型。其實(shí)你錯(cuò)了,人總是被眼睛所看到的東西欺騙,Win API 中根本沒(méi)有什么新的數(shù)據(jù)類型,全都是標(biāo)準(zhǔn)C++中的類型,說(shuō)白了,這些東西全是數(shù)字來(lái)的。如果你不信,自己可以研究一下。

它定義這些名字,只是方便使用罷了,比如下面這樣:

第一個(gè)變量指的是窗口的句柄,第二個(gè)指的是一個(gè)圖標(biāo)的句柄,第三個(gè)是當(dāng)前應(yīng)用程序的實(shí)例句柄,你看看,如果我們所有的句柄都是int,我們就無(wú)法判斷那些類型是專門用來(lái)表示光標(biāo)資源,不知道哪些類型是專用來(lái)表示位圖的句柄了,但是,如果我們這樣:

這樣就很直觀,我一看這名就知道是Brush Handlers,哦,我就明白它是專門用來(lái)管理內(nèi)存中的畫(huà)刷資源的,看,這就很明了,所以,通常這些新定義的類型或者宏,都是取有意義的名字。比如消息,它也是一個(gè)數(shù)字,如果我說(shuō)115代表叫你去滾,但光是一個(gè)115誰(shuí)知道你什么意思,但是,如果我們?yōu)樗x一個(gè)宏:

這樣,只要我SendMessage(hwnd, WM_GET_OUT, NULL, NULL),你就會(huì)收到一條消息,滾到一邊去。

WinMain的第一個(gè)參數(shù)是當(dāng)前應(yīng)用程序的實(shí)例句柄,第二個(gè)參數(shù)是前一個(gè)實(shí)例,比如我把kill.exe運(yùn)行了兩個(gè)實(shí)例,進(jìn)程列表中會(huì)有兩個(gè)kill.exe,這時(shí)候第一次運(yùn)行的實(shí)例號(hào)假設(shè)為0001,就傳遞第一個(gè)參數(shù)hInstance,第二次運(yùn)行的假設(shè)實(shí)例號(hào)為0002,就傳給了hPrevInstance參數(shù)。

lpCmdLine參數(shù)從名字上就猜到了,就是命令行參數(shù),那LPSTR是啥呢,它其實(shí)就是一個(gè)字符串,你可以跟入定義就知道了,它其實(shí)就是char*,指向char的指針,記得我上一篇文章中說(shuō)的指針有創(chuàng)建數(shù)組的功能嗎?對(duì),其實(shí)這里傳入的命令行參數(shù)應(yīng)該是char[ ],這就是我在第一篇文章中要說(shuō)指針的原因。

這里告訴大家一個(gè)技巧,我們?cè)趺粗滥男﹨?shù)是指針類型呢,因?yàn)椴皇撬袇?shù)都有 * 標(biāo)識(shí)。技巧還是在命名上,以后,只要我們看到P開(kāi)頭的,或者LP開(kāi)頭的,都是指針類型。

比如LPWSTR,LPCTSTR,LPRECT等等。

最后一個(gè)參數(shù)nCmdShow是主窗口的顯示方式。它定義了以下宏。

Value Meaning
SW_HIDE
0

Hides the window and activates another window.

SW_MAXIMIZE
3

Maximizes the specified window.

SW_MINIMIZE
6

Minimizes the specified window and activates the next top-level window in the Z order.

SW_RESTORE
9

Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.

SW_SHOW
5

Activates the window and displays it in its current size and position.

SW_SHOWMAXIMIZED
3

Activates the window and displays it as a maximized window.

SW_SHOWMINIMIZED
2

Activates the window and displays it as a minimized window.

SW_SHOWMINNOACTIVE
7

Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, except the window is not activated.

SW_SHOWNA
8

Displays the window in its current size and position. This value is similar toSW_SHOW, except the window is not activated.

SW_SHOWNOACTIVATE
4

Displays a window in its most recent size and position. This value is similar toSW_SHOWNORMAL, except the window is not activated.

SW_SHOWNORMAL
1

Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.


這個(gè)參數(shù)是操作系統(tǒng)傳入的,我們無(wú)法修改它。那么,應(yīng)用程序在運(yùn)行時(shí),是如何決定這個(gè)參數(shù)的呢?看看這個(gè),不用我介紹了吧,你一定很熟悉。


我們寫了WinMain,但我們還要在WinMain前面預(yù)先定義一個(gè)WindowProc函數(shù)。C++與C#,Java這些語(yǔ)言不同,你只需記住,C++編譯器的解析是從左到右,從上到下的,如果某函數(shù)要放到代碼后面來(lái)實(shí)現(xiàn),但在此之前要使用,那么你必須先聲明一下,不然編譯時(shí)會(huì)找不到。這里因?yàn)槲覀兺ǔ?huì)把WindowProc實(shí)現(xiàn)放在WinMain之后,但是在WinMain中設(shè)計(jì)窗口類時(shí)要用到它的指針,這時(shí)候,我們必須在WinMain之前聲明WindowProc。

同樣地,WindowProc的定義我們不用記,到MSDN直接抄就行了。


前導(dǎo)聲明與后面實(shí)現(xiàn)的函數(shù)的簽名必須一致,編譯才會(huì)認(rèn)為它們是同一個(gè)函數(shù)。在WindowProc中返回DefWindowProc是把我們不感興趣或者沒(méi)有處理的消息交回給操作系統(tǒng)來(lái)處理。也許你會(huì)問(wèn),函數(shù)的名字一定要叫WindowProc嗎?當(dāng)然不是了,你可以改為其他名字,如MyProc,但前提是返回值和參數(shù)的類型以及個(gè)數(shù)必須一致。

這個(gè)函數(shù)帶了CALLBACK,說(shuō)明不是我們調(diào)用的,也是由操作系統(tǒng)調(diào)用的,我們?cè)谶@個(gè)函數(shù)里面對(duì)需要處理的消息進(jìn)行響應(yīng)。至于,為什么可以改函數(shù)的名字而系統(tǒng)為什么能找到這個(gè)函數(shù)呢,后面你就知道了。

二、設(shè)計(jì)與注冊(cè)窗口類

設(shè)計(jì)窗口類,其實(shí)就是設(shè)計(jì)我們程序的主窗口,如有沒(méi)有標(biāo)題欄,背景什么顏色,有沒(méi)有邊框,可不可以調(diào)整大小等。要設(shè)計(jì)窗口類,我們用到一個(gè)結(jié)構(gòu)——

通常情況下,我們用WNDCLASS就可以了,當(dāng)然還有一個(gè)WNDCLASSEX的擴(kuò)展結(jié)構(gòu),在API里面,凡是看到EX結(jié)尾的都是擴(kuò)展的意思,比如CreateWindowEx就是CreateWindow的擴(kuò)展函數(shù)。

第一個(gè)成員是窗口的類樣式,注意,不要和窗口樣式(WS_xxxxx)混淆了,這里指的是這個(gè)窗口類的特征,不是窗口的外觀特征,這兩個(gè)style是不一樣的。

它的值可以參考MSDN,通常我們只需要兩個(gè)就可以了——CS_HREDRAW | CS_VREDRAW,從名字就看出來(lái)了,就是同時(shí)具備水平重畫(huà)和垂直重畫(huà)。因?yàn)楫?dāng)我們的窗口顯示的時(shí)候,被其他窗口擋住后重新顯示,或者大小調(diào)整后,窗口都要發(fā)生繪制,就像我們?cè)诩埳贤盔f一樣,每次窗口的變化都會(huì)“粉刷”一遍,并發(fā)送WM_PAINT消息。

lpfnWndProc參數(shù)就是用來(lái)設(shè)置你用哪個(gè)WindowProc來(lái)處理消息,前面我說(shuō)過(guò),我們只要不更改回調(diào)函數(shù)的返回值和參數(shù)的類型和順序,就可以隨意設(shè)置函數(shù)的名字,那為什么系統(tǒng)可以找到我們用的回調(diào)函數(shù)呢,對(duì)的,就是通過(guò)lpfnWndProc傳進(jìn)去的,它是一個(gè)函數(shù)指針,也就是它里面保存的是我們定義的WindowProc的入口地址,使用很簡(jiǎn)單,我們只需要把函數(shù)的名字傳給它就可以了。

cbClsExtra和cbWndExtra通常不需要,設(shè)為0就OK。hInstance是當(dāng)前應(yīng)用程序的實(shí)例句柄,從WinMain的hInstance參數(shù)中可以得到。hIcon和hCursor就不用我說(shuō)了,看名字就知道了。

hbrBackground是窗口的背景色,你也可以不設(shè)置,但在處理WM_PAINT消息時(shí)必須繪制窗口背景。也可以直接用系統(tǒng)定義的顏色,MSDN為我們列出這些值,大家不用記,直接到MSDN拿來(lái)用就行了,這些都比較好理解,看名字就知道了。

lpszMenuName指的是菜單的ID,沒(méi)有菜單就NULL,lpszClassName就是我們要向系統(tǒng)注冊(cè)的類名,字符,不能與系統(tǒng)已存在的類名沖突,如“BUTTON”類。

所以,在WinMain中設(shè)計(jì)窗口類。

窗口類設(shè)計(jì)完成后,不要忘了向系統(tǒng)注冊(cè),這樣系統(tǒng)才能知道有這個(gè)窗口類的存在。向操作系統(tǒng)注冊(cè)窗口類,使用RegisterClass函數(shù),它的參數(shù)就是一個(gè)指向WNDCLASS結(jié)構(gòu)體的指針,所以我們傳遞的時(shí)候,要加上&符號(hào)。


三、創(chuàng)建和顯示窗口

窗口類注冊(cè)完成后,就應(yīng)該創(chuàng)建窗口,然后顯示窗口,調(diào)用CreateWindow創(chuàng)建窗口,如果成功,會(huì)返回一個(gè)窗口的句柄,我們對(duì)這個(gè)窗口的操作都要用到這個(gè)句柄。什么是句柄呢?其實(shí)它就是一串?dāng)?shù)字,只是一個(gè)標(biāo)識(shí)而已,內(nèi)存中會(huì)存在各種資源,如圖標(biāo)、文本等,為了可以有效標(biāo)識(shí)這些資源,每一個(gè)資源都有其唯一的標(biāo)識(shí)符,這樣,通過(guò)查找標(biāo)識(shí)符,就可以知道某個(gè)資源存在于內(nèi)存中哪一塊地址中,就好比你出身的時(shí)候,長(zhǎng)輩都要為你取個(gè)名字,你說(shuō)名字用來(lái)干嗎?名字就是用來(lái)標(biāo)識(shí)你的,不然,你見(jiàn)到A叫小明,遇到B又叫小明,那誰(shuí)知道哪個(gè)才是小明?。烤秃孟衲闵洗髮W(xué)去報(bào)到號(hào),會(huì)為你分配一個(gè)可以在本校學(xué)生中唯一標(biāo)識(shí)你的學(xué)號(hào),所有學(xué)生的學(xué)號(hào)都是不同的,這樣,只要通過(guò)索引學(xué)號(hào),就可以找到你的資料。

CreateWindow函數(shù)返回一個(gè)HWND類型,它就是窗口類的句柄。


窗外觀的樣式都是WS_打頭的,是Window Style的縮寫,這個(gè)我就不說(shuō)了,MSDN上全有了。

窗口創(chuàng)建后,就要顯示它,就像我們的產(chǎn)品做了,要向客戶展示。顯示窗口調(diào)用ShowWindow函數(shù)。

既然要顯示窗口了,那么ShowWindow的第一個(gè)參數(shù)就是剛才創(chuàng)建的窗口的句柄,第二個(gè)參數(shù)控制窗口如何顯示,你可以從SW_XXXX中選一個(gè),也可以用WinMain傳進(jìn)來(lái)的參數(shù),還記得WinMain的最后一個(gè)參數(shù)嗎?

四、更新窗口(可選)

為什么更新窗口這一步可有可無(wú)呢?因?yàn)橹灰绦蛟谶\(yùn)行著,只要不是最小化,只要窗口是可見(jiàn)的,那么,我們的應(yīng)用程序會(huì)不斷接收到WM_PAINT通知。這里先不說(shuō),后面你會(huì)明白的。好了,更新窗口,當(dāng)然是調(diào)用UpdateWindow函數(shù)。


五、消息循環(huán)

Windows操作系統(tǒng)是基于消息控制機(jī)制的,用戶與系統(tǒng)之間的交互,程序與系統(tǒng)之間的交互,都是通過(guò)發(fā)送和接收消息來(lái)完成的。就好像軍隊(duì)一樣,命令一旦傳達(dá),就要執(zhí)行,當(dāng)然,我們的應(yīng)用程序和軍隊(duì)不一樣,我們收到指令不一要執(zhí)行,我們是可以選擇性地執(zhí)行。

我們知道,代碼是不斷往前執(zhí)行的,像我們剛才寫的WinMain函數(shù)一樣,如果你現(xiàn)在運(yùn)行程序,你會(huì)發(fā)現(xiàn)什么都沒(méi)有,是不是程序不能運(yùn)行呢,不是,其實(shí)程序是運(yùn)行了,只是它馬上結(jié)束了,只要程序執(zhí)行跳出了WinMain的右大括號(hào),程序就會(huì)結(jié)束了。那么,要如何讓程序不結(jié)束了,可能大家注意到我們?cè)贑程序中可以用一個(gè)getchar()函數(shù)來(lái)等到用戶輸入,這樣程序就人停在那里,直到用戶輸入內(nèi)容。但我們的窗口應(yīng)用不能這樣做,因?yàn)橛脩粲锌赡苓M(jìn)行其他操作,如最小化窗口,移動(dòng)窗口,改變窗口大小,或者點(diǎn)擊窗口上的按鈕等。因此,我們不能簡(jiǎn)地弄一個(gè)getchar在那里,這樣就無(wú)法響應(yīng)用戶的其他操作了。

可以讓程序留在某處不結(jié)束的另一個(gè)方法就是使用循環(huán),而且是死循環(huán),這樣程序才會(huì)永久地停在某個(gè)地方,但這個(gè)死循環(huán)必須具有跳出的條件,不然你的程序會(huì)永久執(zhí)行,直達(dá)停電或者把電腦砸了。

這樣消息循環(huán)就出現(xiàn)了,只要有與用戶交互,系統(tǒng)人不斷地向應(yīng)用程序發(fā)送消息通知,因?yàn)檫@些消息是不定時(shí)不斷發(fā)送的,必須有一個(gè)綬沖區(qū)來(lái)存放,就好像你去銀行辦理手續(xù)要排隊(duì)一樣,我們從最前端取出一條一條消息處理,后面新發(fā)送的消息會(huì)一直在排隊(duì),直到把所有消息處理完,這就是消息隊(duì)列。

要取出一條消息,調(diào)用GetMessage函數(shù)。函數(shù)會(huì)傳入一個(gè)MSG結(jié)構(gòu)體的指針,當(dāng)收到消息,會(huì)填充MSG結(jié)構(gòu)體中的成員變量,這樣我們就知道我們的應(yīng)用程序收到什么消息了,直到GetMessage函數(shù)取不到消息,條件不成立,循環(huán)跳出,這時(shí)應(yīng)用程序就退出。MSG的定義如下:

hwnd不用說(shuō)了,就是窗口句柄,哪個(gè)窗口的句柄?還記得WindowProc回調(diào)函數(shù)嗎?你把這個(gè)函數(shù)交給了誰(shuí)來(lái)處理,hwnd就是誰(shuí)的句柄,比如我們上面的代碼,我們是把WindowProc賦給了新注冊(cè)的窗口類,并創(chuàng)建了主窗口,返回一個(gè)表示主窗口的句柄,所以,這里MSG中的hwnd指的就是我們的主窗口。

message就是我們接收到的消息,看到,它是一個(gè)數(shù)字,無(wú)符號(hào)整型,所以我們操作的所有消息都是數(shù)字來(lái)的。wParam和lParam是消息的附加參數(shù),其實(shí)也是數(shù)值來(lái)的。通常,lParam指示消息的處理結(jié)果,不同消息的結(jié)果(返回值)不同,具體可參閱MSDN。

有了一個(gè)整型的值來(lái)表示消息,我們?yōu)槭裁催€需要附加參數(shù)呢?你不妨想一下,如果接收一條WM_LBUTTONDOWN消息,即鼠標(biāo)左鍵按下時(shí)發(fā)送的通知消息,那么,我們不僅知道左鍵按下這件事,我們更感趣的是,鼠標(biāo)在屏幕上的哪個(gè)坐標(biāo)處按下左鍵,按了幾下,這時(shí)候,你公憑一條WM_LBUTTONDOWN消息是無(wú)法傳遞這么多消息的??赡芪覀冃枰寻聪伦箧I時(shí)的坐標(biāo)放入wParam參數(shù)中;最典型的就是WM_COMMAND消息,因?yàn)橹灰闶褂貌藛?,點(diǎn)擊按鈕都會(huì)發(fā)送這樣一條消息,那么我怎么知道用戶點(diǎn)了哪個(gè)按鈕呢?如果窗口中只有一個(gè)按鈕,那好辦,用戶肯定單擊了它,但是,如果窗口上有10個(gè)按鈕呢?而每一個(gè)按鈕被單擊都會(huì)發(fā)送WM_COMMAND消息,你能知道用戶點(diǎn)擊了哪個(gè)按鈕嗎?所以,我們要把用戶點(diǎn)擊了的那個(gè)按鈕的句柄存到lParam參數(shù)中,這樣一來(lái),我們就可以判斷出用戶到底點(diǎn)擊了哪個(gè)按鈕了。

GetMessage函數(shù)聲明如下:

這個(gè)函數(shù)在定義時(shí)帶了一個(gè)WINAPI,現(xiàn)在,按照前面我說(shuō)的方法,你應(yīng)該猜到,它就是一個(gè)宏,而真實(shí)的值是__stdcall,前文中說(shuō)過(guò)了。

第一個(gè)參數(shù)是以LP開(kāi)頭,還記得嗎,我說(shuō)過(guò)的,你應(yīng)該想到它就是 MSG* ,一個(gè)指向MSG結(jié)構(gòu)的指針。第二個(gè)參數(shù)是句柄,通常我們用NULL,因?yàn)槲覀儠?huì)捕捉整個(gè)應(yīng)用程序的消息。后面兩個(gè)參數(shù)是用來(lái)過(guò)濾消息的,指定哪個(gè)范圍內(nèi)的消息我接收,在此范圍之外的消息我拒收,如果不過(guò)濾就全設(shè)為0.。返回值就不說(shuō)了,自己看。

TranslateMessage是用于轉(zhuǎn)換按鍵信息的,因?yàn)殒I盤按下和彈起會(huì)發(fā)送WM_KEYDOWN和WM_KEYUP消息,但如果我們只想知道用戶輸了哪些字符,這個(gè)函數(shù)可以把這些消息轉(zhuǎn)換為WM_CHAR消息,它表示的就是鍵盤按下的那個(gè)鍵的字符,如“A”,這樣我們處理起來(lái)就更方便了。

DispatchMessage函數(shù)是必須調(diào)用的,它的功能就相當(dāng)于一根傳送帶,每收到一條消息,DispatchMessage函數(shù)負(fù)責(zé)把消息傳到WindowProc讓我們的代碼來(lái)處理,如果不調(diào)用這個(gè)函數(shù),我們定義的WindowProc就永遠(yuǎn)接收不到消息,你就不能做消息響應(yīng)了,你的程序就只能從運(yùn)行就開(kāi)始死掉了,沒(méi)有響應(yīng)。


六、消息響應(yīng)

其實(shí)現(xiàn)在我們的應(yīng)用程序是可以運(yùn)行了,因?yàn)樵赪indowProc中我們調(diào)用了DefWindowProc,函數(shù),消息我們不作任何處理,又把控制權(quán)路由回到操作系統(tǒng)來(lái)默認(rèn)處理,所以,整個(gè)過(guò)程中,我們現(xiàn)在的消息循環(huán)是成立的,只不過(guò)我們不做任何響應(yīng)罷了。

好的,現(xiàn)在我把完整的代碼貼一下,方便你把前面我們說(shuō)的內(nèi)容串聯(lián)起來(lái)。

  1. #include <Windows.h>   
  2. // 必須要進(jìn)行前導(dǎo)聲明   
  3. LRESULT CALLBACK WindowProc(  
  4.     _In_  HWND hwnd,  
  5.     _In_  UINT uMsg,  
  6.     _In_  WPARAM wParam,  
  7.     _In_  LPARAM lParam  
  8. );  
  9.   
  10. // 程序入口點(diǎn)   
  11. int CALLBACK WinMain(  
  12.     _In_  HINSTANCE hInstance,  
  13.     _In_  HINSTANCE hPrevInstance,  
  14.     _In_  LPSTR lpCmdLine,  
  15.     _In_  int nCmdShow  
  16.   )  
  17. {  
  18.     // 類名   
  19.     WCHAR* cls_Name = L"My Class";  
  20.     // 設(shè)計(jì)窗口類   
  21.     WNDCLASS wc;  
  22.     wc.hbrBackground = (HBRUSH)COLOR_WINDOW;  
  23.     wc.lpfnWndProc = WindowProc;  
  24.     wc.lpszClassName = cls_Name;  
  25.     wc.hInstance = hInstance;  
  26.     // 注冊(cè)窗口類   
  27.     RegisterClass(&wc);  
  28.   
  29.     // 創(chuàng)建窗口   
  30.     HWND hwnd = CreateWindow(  
  31.         cls_Name,           //類名,要和剛才注冊(cè)的一致   
  32.         L"我的應(yīng)用程序",  //窗口標(biāo)題文字   
  33.         WS_OVERLAPPEDWINDOW, //窗口外觀樣式   
  34.         38,             //窗口相對(duì)于父級(jí)的X坐標(biāo)   
  35.         20,             //窗口相對(duì)于父級(jí)的Y坐標(biāo)   
  36.         480,                //窗口的寬度   
  37.         250,                //窗口的高度   
  38.         NULL,               //沒(méi)有父窗口,為NULL   
  39.         NULL,               //沒(méi)有菜單,為NULL   
  40.         hInstance,          //當(dāng)前應(yīng)用程序的實(shí)例句柄   
  41.         NULL);              //沒(méi)有附加數(shù)據(jù),為NULL   
  42.     if(hwnd == NULL) //檢查窗口是否創(chuàng)建成功   
  43.         return 0;  
  44.   
  45.     // 顯示窗口   
  46.     ShowWindow(hwnd, SW_SHOW);  
  47.   
  48.     // 更新窗口   
  49.     UpdateWindow(hwnd);  
  50.   
  51.     // 消息循環(huán)   
  52.     MSG msg;  
  53.     while(GetMessage(&msg, NULL, 0, 0))  
  54.     {  
  55.         TranslateMessage(&msg);  
  56.         DispatchMessage(&msg);  
  57.     }  
  58.     return 0;  
  59. }  
  60. // 在WinMain后實(shí)現(xiàn)   
  61. LRESULT CALLBACK WindowProc(  
  62.     _In_  HWND hwnd,  
  63.     _In_  UINT uMsg,  
  64.     _In_  WPARAM wParam,  
  65.     _In_  LPARAM lParam  
  66. )  
  67. {  
  68.     return DefWindowProc(hwnd, uMsg, wParam, lParam);  
  69. }  


所有代碼看上去貌似很正常,也遵守了流程,設(shè)計(jì)窗口類,注冊(cè)窗口類,創(chuàng)建窗口,顯示窗口,更新窗口,消息循環(huán)。是吧,這段代碼看上去毫無(wú)破綻,運(yùn)行應(yīng)該沒(méi)問(wèn)題吧。好,如果你如此自信,那就試試吧。

按下F5試試運(yùn)行。
哈哈,結(jié)果會(huì)讓很多人失望,很多初學(xué)者就是這樣,一切看起來(lái)好像正常,于是有人開(kāi)始罵VC是垃圾,是編譯器有bug,也有人開(kāi)始想放棄了,媽的,這么難,不學(xué)了。人啊,總是這樣,老指責(zé)別人的問(wèn)題,從不在自己身上找問(wèn)題,是真的VC的bug嗎?

我前面說(shuō)了,這段代碼貌似很正常,呵呵,你看到問(wèn)題在哪嗎?給你兩分鐘來(lái)找錯(cuò)。我提示一下,這個(gè)程序沒(méi)有運(yùn)行是因?yàn)橹鞔翱诟揪蜎](méi)有創(chuàng)建,因?yàn)槲以诖a里面做了判斷,如果窗口順柄hwnd為NULL,就退出,現(xiàn)在程序一運(yùn)行就退出了,明顯是窗口創(chuàng)建失敗。

…………

好了,不用找了,很多人找不出來(lái),尤其是許多初學(xué)者,不少人找了一遍又一遍,都說(shuō)沒(méi)有錯(cuò)誤,至少代碼提示沒(méi)說(shuō)有錯(cuò),編譯運(yùn)行也沒(méi)報(bào)錯(cuò),所以不少人自信地說(shuō),代碼沒(méi)錯(cuò)。

其實(shí)你是對(duì)的,代碼確實(shí)沒(méi)有錯(cuò),而問(wèn)題就出在WNDCLASS結(jié)構(gòu)上,認(rèn)真看一下MSDN上有關(guān)RegisterClass函數(shù)說(shuō)明中的一句話,這句話很多人沒(méi)注意到,但它很關(guān)鍵。

You must fill the structure with the appropriate class attributes before passing it to the function.

現(xiàn)在你明白了吧,還不清楚?沒(méi)關(guān)系,看看我把代碼這樣改一下你就知道了。

現(xiàn)在,你運(yùn)行一下,你一定能看到窗口。

但現(xiàn)在你對(duì)窗口無(wú)法進(jìn)行操作,因?yàn)楹罄m(xù)的代碼還沒(méi)完成。

為什么現(xiàn)在又可以了呢?MSDN那句話的意思就是說(shuō)我們?cè)谧?cè)窗口類之前必須填充WNDCLASS結(jié)構(gòu)體,何為填充,就是要為結(jié)構(gòu)的所有成員賦值,就算不需要你也要為它賦一個(gè)NULL或0,因?yàn)榻Y(jié)構(gòu)在創(chuàng)建時(shí)沒(méi)有對(duì)成員進(jìn)行初始化,這就導(dǎo)致變量無(wú)法正確的分配內(nèi)存,最后注冊(cè)失敗。

那么,如果一個(gè)結(jié)構(gòu)體成員很多,而我只需要用到其中三個(gè),其他的也要初始化,是不是很麻煩,是的,除了為每個(gè)成員賦值,還有一種較簡(jiǎn)單的方法,就是在聲明變量時(shí)給它賦一對(duì)大括號(hào),里面放置結(jié)構(gòu)體的應(yīng)該分配內(nèi)存的大小,如:

這樣一來(lái),我們也發(fā)現(xiàn),窗口也可以成功創(chuàng)建。

我們還可以更簡(jiǎn)單,直接把sizeof也去掉,在聲明變量時(shí),直接賦一對(duì)空的大括號(hào)就行了,就如這樣。

這樣寫更簡(jiǎn)單,窗口類同樣可以正常注冊(cè)。大括號(hào)代表的是代碼塊,這樣,結(jié)構(gòu)體有了一個(gè)初值,因此它會(huì)按照結(jié)構(gòu)體的大小分配了相應(yīng)的內(nèi)存。

為什么會(huì)這樣呢?這里涉及到一個(gè)關(guān)于結(jié)構(gòu)體的一個(gè)很有趣的賦值方式。我們先放下我們這個(gè)例子,下面我寫一個(gè)簡(jiǎn)單的例子,你就明白了。

在本例中,我們定義了一個(gè)表示矩形的結(jié)構(gòu)體 RECT ,它有四個(gè)成員,分別橫坐標(biāo),縱坐標(biāo),寬度,高度,但是,我們?cè)诼暶骱唾x值中,我們只用了一對(duì)大括號(hào),把每個(gè)成員的值,按照定義的順序依次寫到大括號(hào)中,即{ 0, 0, 20, 30 },x的值為0,y的值為0,width為20,height的值為30。

也就是說(shuō),我們可以通過(guò)這種簡(jiǎn)單的方法向結(jié)構(gòu)變量賦值,注意值的順序要和成員變量定義的順序相同。

現(xiàn)在,回到我們的Windows程序來(lái),我們明白了這種賦值方式,對(duì)于 WNDCLASS wc = { } 就不難理解了,這樣雖然大括號(hào)里面是空的,其實(shí)它已經(jīng)把變量初始化了,都賦了默認(rèn)值,這樣一來(lái),就可以正確分配內(nèi)存了。

七、為什么不能退出

通常情況下,當(dāng)我們的主窗口關(guān)閉后,應(yīng)用程序應(yīng)該退出(木馬程序除外),但是,我們剛才運(yùn)行后發(fā)現(xiàn),為什么我的窗口關(guān)了,但程序不退出呢?前面我說(shuō)了,要退出程序,就要先跳出消息循環(huán),和關(guān)閉哪個(gè)窗口無(wú)關(guān)。因此,我們要解決兩個(gè)問(wèn)題:

1、如果跳出消息循環(huán);

2、什么時(shí)候退出程序。

其實(shí)兩個(gè)問(wèn)題是可以合并到一起解決。

首先要知道,當(dāng)窗口被關(guān)閉,為窗口所分配的內(nèi)存會(huì)被銷毀,同時(shí),我們會(huì)收到一條WM_DESTROY消息,因而,我們只要在收到這條消息時(shí)調(diào)用PostQuitMessage函數(shù),這個(gè)函數(shù)提交一條WM_QUIT消息,而在消息循環(huán)中,GetMessage函數(shù)是不接收WM_QUIT消息的,這樣一來(lái),GetMessage返回FALSE,就可以跳出消息循環(huán)了,這樣應(yīng)用程序就可以退出了。

所以,我們要做的就是捕捉WM_DESTROY消息,然后PostQuitMessage.


我們會(huì)收到很多消息,所以用switch判斷一下是不是WM_DESTROY消息,如果是,退出應(yīng)用程序。

好了,這樣,我們一個(gè)完整的Windows應(yīng)用程序就做好了。


下面是完整的代碼清單。

  1. #include <Windows.h>   
  2. // 必須要進(jìn)行前導(dǎo)聲明   
  3. LRESULT CALLBACK WindowProc(  
  4.     _In_  HWND hwnd,  
  5.     _In_  UINT uMsg,  
  6.     _In_  WPARAM wParam,  
  7.     _In_  LPARAM lParam  
  8. );  
  9.   
  10. // 程序入口點(diǎn)   
  11. int CALLBACK WinMain(  
  12.     _In_  HINSTANCE hInstance,  
  13.     _In_  HINSTANCE hPrevInstance,  
  14.     _In_  LPSTR lpCmdLine,  
  15.     _In_  int nCmdShow  
  16.   )  
  17. {  
  18.     // 類名   
  19.     WCHAR* cls_Name = L"My Class";  
  20.     // 設(shè)計(jì)窗口類   
  21.     WNDCLASS wc = { };  
  22.     wc.hbrBackground = (HBRUSH)COLOR_WINDOW;  
  23.     wc.lpfnWndProc = WindowProc;  
  24.     wc.lpszClassName = cls_Name;  
  25.     wc.hInstance = hInstance;  
  26.     // 注冊(cè)窗口類   
  27.     RegisterClass(&wc);  
  28.   
  29.     // 創(chuàng)建窗口   
  30.     HWND hwnd = CreateWindow(  
  31.         cls_Name,           //類名,要和剛才注冊(cè)的一致   
  32.         L"我的應(yīng)用程序",  //窗口標(biāo)題文字   
  33.         WS_OVERLAPPEDWINDOW, //窗口外觀樣式   
  34.         38,                 //窗口相對(duì)于父級(jí)的X坐標(biāo)   
  35.         20,                 //窗口相對(duì)于父級(jí)的Y坐標(biāo)   
  36.         480,                //窗口的寬度   
  37.         250,                //窗口的高度   
  38.         NULL,               //沒(méi)有父窗口,為NULL   
  39.         NULL,               //沒(méi)有菜單,為NULL   
  40.         hInstance,          //當(dāng)前應(yīng)用程序的實(shí)例句柄   
  41.         NULL);              //沒(méi)有附加數(shù)據(jù),為NULL   
  42.     if(hwnd == NULL) //檢查窗口是否創(chuàng)建成功   
  43.         return 0;  
  44.   
  45.     // 顯示窗口   
  46.     ShowWindow(hwnd, SW_SHOW);  
  47.   
  48.     // 更新窗口   
  49.     UpdateWindow(hwnd);  
  50.   
  51.     // 消息循環(huán)   
  52.     MSG msg;  
  53.     while(GetMessage(&msg, NULL, 0, 0))  
  54.     {  
  55.         TranslateMessage(&msg);  
  56.         DispatchMessage(&msg);  
  57.     }  
  58.     return 0;  
  59. }  
  60. // 在WinMain后實(shí)現(xiàn)   
  61. LRESULT CALLBACK WindowProc(  
  62.     _In_  HWND hwnd,  
  63.     _In_  UINT uMsg,  
  64.     _In_  WPARAM wParam,  
  65.     _In_  LPARAM lParam  
  66. )  
  67. {  
  68.     switch(uMsg)  
  69.     {  
  70.     case WM_DESTROY:  
  71.         {  
  72.             PostQuitMessage(0);  
  73.             return 0;  
  74.         }  
  75.     }  
  76.     return DefWindowProc(hwnd, uMsg, wParam, lParam);  
  77. }  

原文出處:http://blog.csdn.net/tcjiaan/article/details/

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多