Windows編程 第三回 Windows程序的生與死(中)

自家之言

 我認爲,我們對任何事物的認知過程都是循序漸進的,正如我們對人生的看法,當你在20歲,30歲,40歲乃至50歲時對人生看法肯定不一樣,並且 是逐步加深的。我們都聽說過這句話“不聽老人言,吃虧在眼前”,有時長輩給你嘮叨一些人生寶貴經驗與心得時,當時你不理解,或許對它不屑於故,或許認爲它 太老套,直到以後的某天吃虧時才發現當初長輩的話是對的。(請珍視長輩的教導,你不認同多半源於你的無知與輕薄)

學習也是這個道理,對一種知識的認識與掌握也隨着我們的成長而不斷加深。同時我們對知識的接受能力與理解在不同的階段也是不同的(吃虧前就沒理解老 人“言”,吃虧後就理解了)。如同乍一接觸windows編程,這對我們來說是一個全新的世界,我們的接受能力與理解能力處在最弱的一個階段,因此我認爲 我們應該學習最簡單的東西來熟悉它,就是所謂的“入門”,“入門”之後再層層深入。如同我們在初中、高中和大學都學物理,講的知識點還是那些,但是對知識 點的講解深度是一步一步的加深的。因此我在上一回中的講解很淺顯,很菜鳥(本人本身就是菜鳥),很不專業,難免有出錯的地方,只是希望把初學者領進這全新 的世界快點熟悉它,好入門。最權威最正確的最詳細的知識介紹未必對初學者是最好的教材;很淺顯易懂,難免有點錯誤,能帶大家入門的知識介紹未必就是最差 的。(大家學深入後,如果發現其中真有錯誤,別忘通知我。這也符合人類對科學的認知過程:學習,再深入的學習,推翻原來的錯誤,樹立新的當下認爲正確理 論,可能新的當下認爲正確的理論再會被後人推翻——沒有絕對“最正確”,只是我們又向“最正確”逼近了一步)

因此對知識介紹不在於最權威,而在於介紹最合適讀者接受,所謂的“因材施教”吧。我有時感嘆,當下的很多教材,適用對象層次很不分明(有時更本就沒有區分),講
同一方面的書總有一種感覺是一個模子裏刻出來的,對菜鳥太深(看不太懂,反而增加了學習的畏懼心理,希望通過我的努力,儘量降低對新讀者的畏懼心理),對老鳥太淺(看了沒啥價值)……

真誠的希望當下作家,對不同讀者有區分度的出書,也別把書出的千篇一律,都是一個樣子,對一個知識的講解就一定要遵循前人嗎?

吐槽

我本以爲上一回很難寫,這一回簡單點,哪知這一回寫得卻比上一會還要更讓我吐血,心累。主要是這一回信息量太大(原本windows程序的生與死要 寫兩回,看來要改成三回了)。我總是處在一種猶豫當中,即想讓讀者掌握現階段該掌握的(以後會用函數就行了,就像讓剛會開車的新手能掌握各項操作就行了, 此時沒必要過深的掌握各項操作的原理,一是我們掌握不了,二是我們以後未必都要幹設計汽車的或修車的這一行)。但是我又怕讀者理解困難,總是想放更多的資 料幫助理解;放的資料太多,我又怕嚇着讀者繼續學習,或者讓讀者迷失在資料當中而忘了我們的目的(就是掌握函數基本用法,會用就行)。思來想去,於是我打 算把講解程序框架的內容(我認爲最重要的)放在本回,把一些補充內容、註釋、一些次重要的概念、函數用法和我搜集的一些資料放在下回,但願我的決策是對 的。

上一回我對windows程序進行了“寫意”式的介紹,這一回我可就要來“工筆”式的描述了。這一回我講解的會更多更詳細,相信有前面的基礎,大家會順利過關的。你準備好了嗎?我們開始了!

毛舉縷析講代碼

爲方便大家閱讀,我再把代碼貼一遍。

1 #include <windows.h>//
2 LRESULT CALLBACK WinSunProc( 3 HWND hwnd, // handle to window
4 UINT uMsg, // message identifier
5 WPARAM wParam, // first message parameter
6 LPARAM lParam // second message parameter
7 ); 8 int WINAPI WinMain( 9 HINSTANCE hInstance, // handle to current instance
10 HINSTANCE hPrevInstance, // handle to previous instance
11 LPSTR lpCmdLine, // command line
12 int nCmdShow // show state
13 ) 14 { 15 WNDCLASS wndcls; //定義一個wndcls窗口類對象
16 wndcls.cbClsExtra=0; //類變量佔用的存儲空間
17 wndcls.cbWndExtra=0; //實例變量佔用的存儲空間
18 wndcls.hbrBackground=(HBRUSH)GetStockObject(WHILE_BRUSH);//指定窗口類畫刷句柄
19 wndcls.hCursor=LoadCursor(NULL,IDC_ARROW); //指定窗口類光標句柄
20 wndcls.hIcon=LoadIcon(NULL,IDI_APPLICATION); //指定窗口類圖標句柄
21 wndcls.hInstance=hInstance; //包含窗口過程的程序的實例句柄
22 wndcls.lpfnWndProc=WinSunProc; //指向窗口過程函數
23 wndcls.lpszClassName="Hello World"; //指定窗口類名字
24 wndcls.lpszMenuName=NULL; //指定菜單資源名字
25 wndcls.style=CS_HREDRAW | CS_VREDRAW; //指定窗口類型樣式
26 if (!RegisterClass(&wndclass)) return FALSE; //註冊窗口
27 HWND hwnd; //定義句柄變量
28 hwnd=CreateWindow //創建窗口,返回系統爲窗口分配的句柄
29 ("Hello World", //類名,指定該窗口所屬的類
30 "Hello World Program", //窗口的名字,即在標題欄中顯示的文本
31 WS_OVERLAPPEDWINDOW, //該窗口的風格
32 0, //窗口左上角相對於屏幕左上角的初始X座標
33 0, //窗口左上角相對於屏幕左上角的初始Y座標
34 600, //窗口的寬度
35 400, //窗口的高度
36 NULL, //一個子窗口的父窗口的句柄,或隸屬窗口的擁有者窗口的句柄
37 NULL, //菜單句柄
38 hInstance, //創建窗口對象的應用程序的實例句柄
39 NULL); //創建窗口時指定的額外參數
40 ShowWindow(hwnd,SW_SHOWNORMAL);// 顯示窗口
41 UpdateWindow(hwnd); //更新窗口
42 MSG msg; //定義消息結構體變量
43 while(GetMessage(&msg,NULL,0,0)) 44 { 45 TranslateMessage(&msg); 46 DispatchMessage(&msg); 47 } 48 return msg.wParam; 49 } 50 LRESULT CALLBACK WinSunProc( 51 HWND hwnd, // handle to window
52 UINT uMsg, // message identifier
53 WPARAM wParam, // first message parameter
54 LPARAM lParam // second message parameter
55 ) 56 { 57 switch(uMsg) 58 { 59 case WM_CREATE: 60 MessageBox(hwnd,"Window Created","message",MB_OK); 61 break; 62 case WM_PAINT: 63 HDC hDC; 64 PAINTSTRUCT ps; 65 hDC=BeginPaint(hwnd,&ps); 66 TextOut(hDC,0,0,"Hello World!",strlen("Hello World!")); 67 EndPaint(hwnd,&ps); 68 break; 69 case WM_CLOSE: 70 if(IDYES==MessageBox(hwnd,"是否真的結束?","message",MB_YESNO)) 71 { 72 DestroyWindow(hwnd); 73 } 74 break; 75 case WM_DESTROY: 76 PostQuitMessage(0); 77 break; 78 default: 79 return DefWindowProc(hwnd,uMsg,wParam,lParam); 80 } 81 return 0; 82 }

我將按我們一般寫代碼的順序帶大家研究代碼。

 

 

孫新老師如是說(下文中引號內部分):

“接觸過Windows 編程方法的讀者都知道,在應用程序中有一個重要的函數WinMain,這個函數是應用程序的基礎。當Windows 操作系統啓動一個程序時,它調用的就是該程序的WinMain 函數。WinMain 是Windows程序的入口點函數,與DOS 程序的入口點函數main 的作用相同,當WinMain 函數結束或返回時,Windows 應用程序結束。”

 

那我們就先來講解WinMain函數。

 

“WinMain 函數的原型聲明如下:

int WINAPI WinMain(

HINSTANCE hInstance, // handle tocurrent instance(應用程序的實例句柄)

HINSTANCE hPrevInstance,// handle to previous instance(該應用程序前一個實例的句柄)

LPSTR lpCmdLine, // command line(命令行參數串)

int nCmdShow // show state(程序在初始化時如何顯示窗口)

);

WinMain 函數接收4 個參數,這些參數都是在系統調用WinMain 函數時傳遞給應用

程序的。

第一個參數 hInstance 表示該程序當前運行的實例的句柄(解釋見下),這 是一個數值。當程序在Windows 下運行時,它唯一標識運行中的實例(注意,只有運行中的程序實例,纔有實例句柄)。一個應用程序可以運行多個實例,每運行一個實例,系統都會給該實例分配 一個句柄值,並通過hInstance 參數傳遞給WinMain 函數。

第二個參數 hPrevInstance 表示當前實例的前一個實例的句柄。通過查看MSDN 我們可以知道,在Win32 環境下,這個參數總是NULL,即在Win32 環境下,這個參數不再起作用。(可以忽略掌握)

第三個參數 lpCmdLine 是一個以空終止的字符串,指定傳遞給應用程序的命令行參數。例如:在D 盤下有一個sunxin.txt 文件,當我們用鼠標雙擊這個文件時將啓動記事本程序(notepad.exe),此時系統會將D:\sunxin.txt作爲命令行參數傳遞給記事本程序 的WinMain函數,記事本程序在得到這個文件的全路徑名後,就在窗口中顯示該文件的內容。要在VC++開發環境中嚮應用程序傳遞參數,可以單擊菜單 【Project】→【Settings】,選擇“Debug”選項卡,在“Programarguments”編輯框中輸入你想傳遞給應用程序的參數。

第四個參數 nCmdShow 指定程序的窗口應該如何顯示,例如最大化、最小化、隱藏等。”

概念講解:

     句柄(HANDLE)——是Windows 程序中一個重要的概念,使用也非常頻繁。在Windows 程序中,有各種各樣的資源(窗口、圖標、光標等),系統在創建這些資源時會爲它們分配內存,並返回標識這些資源的標識號,即句柄。在後面的內容中我們還會 看到窗口句柄(HWND)圖標句柄(HICON)、光標句柄(HCURSOR)和畫刷句柄(HBRUSH)。在Windows中,對象使用句柄進行標識, 這樣,通過使用一個句柄,應用程序可以訪問一個對象。句柄是一個數,但實際情況並不這樣簡單,它的長度將會隨着不同的計算機平臺和Windows的發展而 有所變化,例如,在32位Windows中,句柄將是一個32位的數據,並且不是整數類型。(這是一個越說越複雜的概念,還是從應用中慢慢體會吧,目前我 們的目標是會用它,有興趣的可以看一下注釋②,不要過分研究註釋而忽略我們只要會用它的目的,不要走火入魔,我提供註釋僅是爲了力求全面)

 

其次來講解窗口類(行15-25)

 

請回憶上一篇文章中,我引用孫鑫老師關於做填空題的比喻,在Windows中要達到做填空題的效果,只能通過結構體來完成。在Windows中,窗口類是在類型爲WNDCLASS的結構變量中定義的結構類型WNDCLASS的定義如下:

typedef struct _WNDCLASS {

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HANDLE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCTSTR lpszMenuName;

LPCTSTR lpszClassName;

} WNDCLASS;

 

“下面對該結構體的成員變量做一個說明。(可以不必太深究每個成員具體的含義,現在有看不懂的正常,以後慢慢體會吧,只要我們會“填空”得到我們想要的窗口就行)

第一個成員變量 style 指定這一類型窗口的樣式,常用的樣式如下:

n CS_HREDRAW

當窗口水平方向上的寬度發生變化時,將重新繪製整個窗口。當窗口發生重繪(不太懂就跳過,我以後的文章會再講)時,窗口中的文字和圖形將被擦除。如果沒有指定這一樣式,那麼在水平方向上調整窗口寬度時,將不會重繪窗口。

n CS_VREDRAW

當窗口垂直方向上的高度發生變化時,將重新繪製整個窗口。如果沒有指定這一樣式,那麼在垂直方向上調整窗口高度時,將不會重繪窗口。

n CS_NOCLOSE

禁用系統菜單的 Close 命令,這將導致窗口沒有關閉按鈕。

n CS_DBLCLKS

當用戶在窗口中雙擊鼠標時,向窗口過程發送鼠標雙擊消息。

style 成員的其他取值請參閱MSDN。(這些具體效果可以運行程序試一下)③

第二個成員變量lpfnWndProc 是一個函數指針,指向窗口過程函數本例即WinSunProc,窗口過程函數是一個回調函數④。

第三個成員變量cbClsExtra:Windows 爲系統中的每一個窗口類管理一個WNDCLASS 結構。在應用程序註冊一個窗口類時,它可以讓Windows 系統爲WNDCLASS 結構分配和追加一定字節數的附加內存空間,這部分內存空間稱爲類附加內存,由屬於這種窗口類的所有窗口所共享,類附加內存空間用於存儲類的附加信息。 Windows 系統把這部分內存初始化爲0。一般我們將這個參數設置爲0。(僅知道黑體字就好了)

第四個成員變量 cbWndExtra:Windows 系統爲每一個窗口管理一個內部數據結構,在

註冊一個窗口類時,應用程序能夠指定一定字節數的附加內存空間,稱爲窗口附加內存。

在創建這類窗口時,Windows 系統就爲窗口的結構分配和追加指定數目的窗口附加內存空

間,應用程序可用這部分內存存儲窗口特有的數據。Windows 系統把這部分內存初始化爲

0。如果應用程序用WNDCLASS 結構註冊對話框(用資源文件中的CLASS 僞指令創建),

必須給DLGWINDOWEXTRA設置這個成員。一般我們將這個參數設置爲0。(僅知道黑體字就好了)

第五個成員變量hInstance 指定包含窗口過程的程序的實例句柄WinMain參數中的hInstance,系統爲我們分配好了。

第六個成員變量 hIcon 指定窗口類的圖標句柄。這個成員變量必須是一個圖標資源的句柄,如果這個成員爲NULL,那麼系統將提供一個默認的圖標。(大家可以自己試一下)

第七個成員變量hCursor 指定窗口類的光標句柄。這個成員變量必須是一個光標資源的句柄。如果這個成員爲NULL,那麼鼠標進入到應用程序窗口中時它的形狀可就不太確定了(不信你可以試一下)。所以一般應用程序都必須明確地設置光標的形狀。

第八個成員變量hbrBackground 指定窗口類的背景畫刷句柄。當窗口發生重繪時,系統使用這裏指定的畫刷來擦除窗口的背景。我們既可以爲hbrBackground 成員指定一個畫刷的句柄,也可以爲其指定一個標準的系統顏色值。

(LoadIcon,LoadCursor,GetStockObject函數下回講)

第九個成員變量lpszMenuName 是一個以空終止的字符串,指定菜單資源的名字。如果你使用菜單資源的ID 號,那麼需要用MAKEINTRESOURCE 宏⑤來進行轉換。如果將lpszMenuName 成員設置爲NULL,那麼基於這個窗口類創建的窗口將沒有默認的菜單。要注意,菜單並不是一個窗口,很多初學者都誤以爲菜單是一個窗口。

第十個成員變量lpszClassName 是一個以空終止的字符串,指定窗口類的名字。這和汽車的設計類似,設計一款新型號的汽車,需要給該型號的汽車取一個名字。同樣的,設計了一種新類型的窗 口,也要爲該類型的窗口取個名字,這裏我們將這種類型窗口的命名爲“HelloWorld”,後面將看到如何使用這個名稱。”

 

再次來講註冊窗口類(行26)

 

“在設計完汽車後,需要報經國家有關部門審批,批准後才能生產這種類型的汽車。同樣地,設計完窗口類(WNDCLASS)後,需要調用RegisterClass 函數對其進行註冊,註冊成功後,纔可以創建該類型的窗口。註冊函數的原型聲明如下:

ATOM RegisterClass(CONST WNDCLASS*lpWndClass);

該函數只有一個參數,即上一步驟中所設計的窗口類對象的指針。

如果函數成功,返回值是唯一標識已註冊的類的一個原子;如果函數失敗,返回值爲0。”

 

複次來講創建窗口(行27-39)

 

“設計好窗口類並且將其成功註冊之後,就可以用CreateWindow 函數產生這種類型的窗口了。CreateWindow 函數的原型聲明如下:

HWNDCreateWindow(

LPCTSTRlpClassName, // pointer to registered class name

LPCTSTRlpWindowName, // pointer to window name

DWORD dwStyle,// window style

int x, //horizontal position of window

int y, //vertical position of window

int nWidth, //window width

int nHeight, //window height

HWND hWndParent,// handle to parent or owner window

HMENU hMenu, //handle to menu or child-window identifier

HANDLEhInstance, // handle to application instance

LPVOID lpParam// pointer to window-creation data

);

參數lpClassName 指定窗口類的名稱,即我們在步驟1 設計一個窗口類中爲WNDCLASS的 lpszClassName 成員指定的名稱即“Hello World”, 在這裏應該設置爲“Hello World”,表示要產生“Hello World”這一類型的窗口。產生窗口的過程是由操作系統完成的,如果在調用CreateWindow 函數之前,沒有用RegisterClass 函數註冊過名稱爲“Hello World”的窗口類型,操作系統將無法得知這一類型窗口的相關信息,從而導致創建窗口失敗。

參數 lpWindowName 指定窗口的名字。如果窗口樣式指定了標題欄,那麼這裏指定的窗口名字將顯示在標題欄上,本例中標題欄會顯示“HelloWorld Program”。

參數 dwStyle 指定創建的窗口的樣式。⑥就好像同一型號的汽車可以有不同的顏色一樣,同一型號的窗口也可以有不同的外觀樣式。要注意區分WNDCLASS 中的style 成員與CreateWindow 函數的dwStyle 參數,前者是指定窗口類的樣式,基於該窗口類創建的窗口都具有這些樣式(即共性),後者是指定某個具體的窗口的樣式(即個性)。

參數xy,nWidth,nHeight 分別指定窗口左上角的xy座標,窗口的寬度,高度。如果參數x被設爲CW_USEDEFAULT,那麼系統爲窗口選擇默認的左上角座標並忽略y參數。如果參數nWidth 被設爲CW_USEDEFAULT,那麼系統爲窗口選擇默認的寬度和高度,參數nHeight 被忽略。本例中指定窗口左上角座標(相對於屏幕)爲(0,0),窗口寬600像素,高400像素。

參數hWndParent 指定被創建窗口的父窗口句柄,一般爲NULL

參數hMenu 指定窗口菜單的句柄,本例中窗口沒有菜單,故設爲NULL。

參數hInstance 指定窗口所屬的應用程序實例的句柄即WinMain參數中的hInstance,系統爲我們分配好了。

參數lpParam:作爲WM_CREATE消息的附加參數lParam 傳入的數據指針。在創建多文檔界面的客戶窗口時,lpParam 必須指向CLIENTCREATESTRUCT 結構體。多數窗口將這個參數設置爲NULL。(知道這個就行了)

 

如果窗口創建成功,CreateWindow 函數將返回系統爲該窗口分配的句柄,否則,返回NULL。注意,在創建窗口之前應先定義一個窗口句柄變量來接收創建窗口之後返回的句柄值。

 

接着來講顯示及更新窗口

 

“(1)顯示窗口

窗口創建之後,我們要讓它顯示出來,這就跟汽車生產出來後要推向市場一樣。調用函數ShowWindow 來顯示窗口,該函數的原型聲明如下所示:

BOOLShowWindow(

HWNDhWnd, // handle to window

intnCmdShow // show state

);

ShowWindow 函數有兩個參數,第一個參數hWnd 就是在上一步驟中成功創建窗口後返回的那個窗口句柄;第二個參數nCmdShow 指定了窗口顯示的狀態,常用的有以下幾種。

n SW_HIDE:隱藏窗口並激活其他窗口。

n SW_SHOW:在窗口原來的位置以原來的尺寸激活和顯示窗口。

n SW_SHOWMAXIMIZED:激活窗口並將其最大化顯示。

n SW_SHOWMINIMIZED:激活窗口並將其最小化顯示。

n SW_SHOWNORMAL:激活並顯示窗口。如果窗口是最小化或最大化的狀態,系統將其恢復到原來的尺寸和大小。應用程序在第一次顯示窗口的時候應該指定此標誌。關於nCmdShow 參數的詳細內容請參見MSDN。

(2)更新窗口

在調用 ShowWindow 函數之後,我們緊接着調用UpdateWindow 來刷新窗口,就好像我們買了新房子,需要裝修一下。UpdateWindow 函數的原型聲明如下:

BOOLUpdateWindow(

HWNDhWnd // handle to window

);

其參數 hWnd 指的是創建成功後的窗口的句柄。”

 

到此,一個窗口就算創建完成了。

 

然後講消息循環

 

上一回只提到消息,到底消息是什麼樣子的呢?在 Windows 程序中,消息是由MSG 結構體⑦來表示的,結構體重包含消息的很多信息,由系統自動填充,我們不用操心。

在創建窗口、顯示窗口、更新窗口後,我們需要編寫一個消息循環,不斷地從消息隊列中取出消息,並進行響應。要從消息隊列中取出消息,我們需要調用GetMessage()函數,該函數的原型聲明如下:

BOOL GetMessage(

LPMSG lpMsg, //address of structure with message

HWND hWnd, //handle of window

UINTwMsgFilterMin, // first message

UINTwMsgFilterMax // last message

);

參數lpMsg 指向一個消息(MSG)結構體,GetMessage 從線程(現在不管這個概念,在以後的文章中會講)的消息隊列中取出的消息信息將保存在該結構體對象中。

參數hWnd 指定接收屬於哪一個窗口的消息。通常我們將其設置爲NULL,用於接收屬於調用線程的所有窗口的窗口消息。

參數wMsgFilterMin 指定要獲取的消息的最小值,通常設置爲0。

參數wMsgFilterMax 指定要獲取的消息的最大值。如果wMsgFilterMin 和wMsgFilterMax 都設置爲0,則接收所有消息。

 

GetMessage 函數接收到除WM_QUIT (看着眼熟了吧,在前面是否看過類似的東東,可以參考一下注釋⑦)外的消息均返回非零值。對於WM_QUIT 消息,該函數返回零。如果出現了錯誤,該函數返回-1,例如,當參數hWnd 是無效的窗口句柄或lpMsg 是無效的指針時。

 

通常我們編寫的消息循環代碼如下:

MSGmsg;

while(GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

前面已經介紹了,GetMessage 函數只有在接收到WM_QUIT 消息時,才返回0。此時while 語句判斷的條件爲假,循環退出,程序纔有可能結束運行。在沒有接收到WM_QUIT消息時,Windows 應用程序就通過這個while 循環來保證程序始終處於運行狀態。

TranslateMessage 函數用於將虛擬鍵(這個我暫時不解釋,n回後我講)消息轉換爲字符消息。字符消息被投遞到調用線程的消息隊列中,當下一次調用GetMessage 函數時被取出。當我們敲擊鍵盤上的某個字符鍵時,系統將產生WM_KEYDOWN 和WM_KEYUP 消息。這兩個消息的附加參數(wParam 和lParam)包含的是虛擬鍵代碼和掃描碼等信息,而我們在程序中往往需要得到某個字符的ASCII 碼,TranslateMessage 這個函數就可以將WM_KEYDOWN 和WM_KEYUP 消息的組合轉換爲一條WM_CHAR 消息(該消息的wParam 附加參數包含了字符的ASCII 碼),並將轉換後的新消息投遞到調用線程的消息隊列中。注意,TranslateMessage函數並不會修改原有的消息,它只是產生新的消息並投遞到消 息隊列中。

DispatchMessage 函數分派一個消息到窗口過程,由窗口過程函數對消息進行處理。DispachMessage 實際上是將消息回傳給操作系統,由操作系統調用窗口過程函數對消息進

行處理(響應)。”

(以上幾個函數看不太懂可先跳過,一般就這個格式,照抄就行了。)

 

溫故知新

“既然講到這了,那我們再來回顧一下上一回我們曾提到的Windows 應用程序的消息處理機制(也就是程序運行機制)

(1)操作系統接收到應用程序的窗口消息,將消息投遞到該應用程序的消息隊列中。

(2)應用程序在消息循環中調用GetMessage 函數從消息隊列中取出一條一條的消息。取出消息後,應用程序可以對消息進行一些預處理,例如,放棄對某些消息的響應,或者調用TranslateMessage 產生新的消息。

(3)應用程序調用DispatchMessage,將消息回傳給操作系統。消息是由MSG 結構體對象來表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage 函數總能進行正確的傳遞。

(4)系統利用WNDCLASS 結構體的lpfnWndProc 成員保存的窗口過程函數的指針調用窗口過程,對消息進行處理(即“系統給應用程序發送了消息”)。”

 

最後編寫窗口過程函數

“在完成上述步驟後,剩下的工作就是編寫一個窗口過程函數,用於處理髮送給窗口的消息。一個Windows 應用程序的主要代碼部分就集中在窗口過程函數中。在MSDN 中可以查到窗口過程函數的聲明形式,如下所示:

LRESULT CALLBACKWindowProc(

HWND hwnd, //handle to window

UINT uMsg, //message identifier

WPARAM wParam,// first message parameter

LPARAM lParam //second message parameter

);

窗口過程函數的名字可以隨便取,如WinSunProc,但函數定義(行50-55)的形式必須和上述聲明(行2-7,由於WinMain函數中有對此函數的參考,即行22,所以要在WinMain函數之前聲明)的形式相同。

提示:系統通過窗口過程函數的地址(指針)來調用窗口過程函數,而不是名字。

WindowProc 函數的4 個參數分別對應消息的窗口句柄、消息代碼、消息代碼的兩個附加參數。(後三個參數知道名字就行了)由於一個程序可以有多個窗口,窗口過程函數的第1 個參數hwnd 就標識了接收消息的特定窗口,就是CreateWindow返回的那個句柄。

在窗口過程函數內部使用 switch/case 語句來確定窗口過程接收的是什麼消息,以及如何對這個消息進行處理。”

 

聲明:本文大部分講解援引自孫鑫老師的《VC++深入詳解》,吃水不忘挖井人,在此向孫鑫老師拜謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章