盡管Windows應用程序千變萬化,令人眼花缭亂,但,消息機制和窗口過程卻始終它們的基礎,掌握了這兩項技術,也就相當於把握住了問題的關鍵。
如果你以前是C程序員或是MFC的忠實用戶,只要你學習過C語言的語法,自己親手編過一些簡短的C程序,理解以下的Win32編程基礎也不是一件困難的事。
一個最簡單的Win32程序 在以前的C語言編程中,一個最簡單的程序可以只有兩行。
void main(void)
{ printf "Hello World!"; }
而要實現同樣功能的Windows程序卻最少也要寫幾十行,這並不是說明Windows應用程序效率低下,難於掌握,只是說明程序在Windows環境下有更豐富的內涵。Windows程序的效率其實不低,在所有的Windows應用程序中,都有一個程序初始化的過程,這得用上幾十條語句,這段初始化的代碼對於任何Windows應用程序而言,都是大同小異的。下面以一個實現最簡單功能的程序EasyWin為例,說明Windows程序的基本框架。
打開Visual C++ 6.0。
選擇File菜單的New,在出現的對話框中,選擇Projects欄目(新建工程),並點取其下的Win32 Application項,表示使用Win32環境創建應用程序。先在Locatin(路徑)中填入“c:\”,然後在Project Name(項目名稱)中填入“EasyWin”,其它按照缺省設置)。單擊OK按鈕。
再次選擇File菜單的New,在出現的對話框中,選擇Files欄目(新建文件),並點取其下的C++ Source File項,表示新建一個C++源文件。在右邊的File欄中輸入“EasyWin”,最後確定讓Add to project檢查框打上勾 )。單擊OK按鈕。
在EasyWin.cpp文件中輸入以下源程序代碼。
//*******************************************************************
// 工程:easywin
// 文件:easywin.cpp
// 內容:一個基本的Win32程序
//*******************************************************************
#include <Windows.h>
#include <Windowsx.h>
//函數聲明
BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
//*******************************************************************
//函數:WinMain()
//功能:Win32應用程序入口函數。創建主窗口,處理消息循環
//*******************************************************************
int PASCAL WinMain( HINSTANCE hInstance, //當前實例句柄
HINSTANCE hPrevInstance, //前一個實例句柄
LPSTR lpCmdLine, //命令行字符
int nCmdShow) //窗口顯示方式
{
MSG msg;
//創建主窗口
if ( !InitWindow( hInstance,
nCmdShow ) )
return FALSE;
//進入消息循環:
//從該應用程序的消息隊列中檢取消息,送到消息處理過程,
//當檢取到WM_QUIT消息時,退出消息循環。
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//程序結束
return msg.wParam;
}
//******************************************************************
//函數:InitWindow()
//功能:創建窗口。
//******************************************************************
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )
{
HWND hwnd; //窗口句柄
WNDCLASS wc; //窗口類結構
//填充窗口類結構
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "EasyWin";
//注冊窗口類
RegisterClass( &wc );
//創建主窗口
hwnd = CreateWindow(
"EasyWin", //窗口類名稱
"一個基本的Win32程序", //窗口標題
WS_OVERLAPPEDWINDOW, //窗口風格,定義為普通型
100, //窗口位置的x坐標
100, //窗口位置的y坐標
400, //窗口的寬度
300, //窗口的高度
NULL, //父窗口句柄
NULL, //菜單句柄
hInstance, //應用程序實例句柄
NULL ); //窗口創建數據指針
if( !hwnd ) return FALSE;
//顯示並更新窗口
ShowWindow( hwnd, nCmdShow );
UpdateWindow( hwnd );
return TRUE;
}
//******************************************************************
//函數:WinProc()
//功能:處理主窗口消息
//******************************************************************
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_KEYDOWN://擊鍵消息
switch( wParam )
{
case VK_ESCAPE:
MessageBox(hWnd,"ESC鍵按下了!",
"Keyboard",MB_OK);
break;
}
break;
case WM_RBUTTONDOWN://鼠標消息
{
MessageBox(hWnd,"鼠標右鍵按下了!","Mouse",MB_OK);
break;
}
case WM_PAINT://窗口重畫消息
{
char hello[]="你好,我是EasyWin !";
HDC hdc;
PAINTSTRUCT ps;
hdc=BeginPaint( hWnd,&ps ); //取得設備環境句柄
SetTextColor(hdc, RGB(0,0,255)); //設置文字顏色
TextOut( hdc, 20, 10, hello, strlen(hello) );//輸出文字
EndPaint( hWnd, &ps ); //釋放資源
break;
}
case WM_DESTROY://退出消息
PostQuitMessage( 0 );//調用退出函數
break;
}
//調用缺省消息處理過程
return DefWindowProc(hWnd, message, wParam, lParam);
}
程序輸入完畢,即可編譯執行。在窗口中擊鼠標鍵或按ESC鍵時,會彈出一個對話框以表示你的操作。
其實,這個程序可以看成是所有Win32應用程序的框架,在以後所有的程序中,你會發現它們都是在這個程序的基礎之上再添加代碼。
WinMain()函數 WinMain()函數是應用程序開始執行時的入口點,通常也是應用程序結束任務退出時的出口點。它與DOS程序的main()函數起同樣的作用,有一點不同的是,WinMain()函數必須帶有四個參數,它們是系統傳遞給它的。WinMain()函數的原型如下:
int PASCAL WinMain( HINSTANCE hInstance, //當前實例句柄
HINSTANCE hPrevInstance, //前一個實例句柄
LPSTR lpCmdLine, //命令行字符
int nCmdShow) //窗口顯示方式
第一個參數hInstance,是標識該應用程序當前的實例的句柄。它是HINSTANCE類型,HINSTANCE是Handle of Instance的縮寫,表示實例的句柄。hInstance是一個很關鍵的數據,它唯一的代表該應用程序,在後面初始化程序主窗口的過程中需要用到這個參數。
這裡有兩個概念,一個是實例,一個是句柄。實例代表的是應用程序執行的整個過程和方法,一個應用程序如果沒有被執行,只是存在於磁盤上,那麼就說它是沒有被實例化的;只要一執行,則說該程序的一個實例在運行。句柄,顧名思義,指的是一個對象的把柄。在Windows中,有各種各樣的句柄,它們都是32位的指針變量,用來指向該對象所占據的內存區。句柄的使用,可以極大的方便Windows管理其內存中的各種對象。
第二個參數是hPrevInstance,它是用來標識該應用程序的前一個實例句柄。對於基於Win32的應用程序來說,這個參數總是NULL。這是因為在Win95操作系統中,應用程序的每個實例都有各自獨立的地址空間,即使同一個應用程序被執行了兩次,在內存中也會為它們的每一個實例分配新的內存空間,所以一個應用程序被執行後,不會有前一個實例存在的可能。也就是說,hPrevInstance這個參數是完全沒有必要的,只是為了提供與16位Windows的應用程序形式上的兼容性,才保留了這個參數。在以前的16位Windows環境下(如Windows3.2),hPrevInstance用來標識與hInstance相關的應用程序的前一個句柄。
第三個參數是lpCmdLine,是指向應用程序命令行參數字符串的指針。
如在Win95的“開始”菜單中單擊“運行”,輸入“easywin hello”,則此參數指向的字符串為“hello”。
最後一個參數是nCmdShow,是一個用來指定窗口顯示方式的整數。這個整數值可以是SW_SHOW、SW_HIDE、SW_SHOWMAXIMIZED、SW_SHOWMINIMIZED等,關於這些值的含義,將在下一節說明。
注冊窗口類 一個應用程序可以有許多窗口,但只有一個是主窗口,它是與該應用程序的實例句柄唯一關聯的。上面的例程中,創建主窗口的函數是InitWindow()。
通常要對填充一個窗口類結構WNDCLASS,然後調用RegisterClass()對該窗口類進行注冊。每個窗口都有一些基本的屬性,如窗口邊框、窗口標題欄文字、窗口大小和位置、鼠標、背景色、處理窗口消息函數的名稱等等。注冊的過程也就是將這些屬性告訴系統,然後再調用CreateWindow()函數創建出窗口。這也就象你去裁縫店訂做一件衣服,先要告訴店老板你的身材尺寸、布料顏色、以及你想要的款式,然後他才能為你做出一件讓你滿意的衣服。
在VC的幫助中,可以看到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;
在Win95和WinNT的具有新界面特性的系統中,為了支持新的窗口界面特性,還有一種擴展的窗口類型WNDCLASSEX,它的定義如下:
typedef struct _WNDCLASSEX {
UINT cbSize; //指定WNDCLASSEX結構的大小
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm; //窗口的小圖標
} WNDCLASSEX;
WNDCLASS和WNDCLASSEX這兩個結構基本上是一致的,只是WNDCLASSEX結構中多了cbSize和hIconSm這兩個成員。WNDCLASS結構的各成員中,其注釋後打了星號的表示該項應特別注意。
WNDCLASS結構的第一個成員style表示窗口類的風格,它往往是由一些基本的風格通過位的“或”操作(操作符位“|”)組合而成。下表列出了一些常用的基本窗口風格:
風格
含義 CS_HREDRAW 如果窗口客戶區寬度發生改變,重繪整個窗口 CS_VREDRAW 如果窗口客戶區高度發生改變,重繪整個窗口 CS_DBLCLKS 能感受用戶在窗口中的雙擊消息 CS_NOCLOSE 禁用系統菜單中的“關閉”命令 CS_OWNDC 為該窗口類的各窗口分配各自獨立的設備環境 CS_CLASSDC 為該窗口類的各窗口分配一個共享的設備環境 CS_PARENTDC 指定子窗口繼承其父窗口的設備環境 CS_SAVEBITS 把被窗口遮掩的屏幕圖象部分作為位圖保存起來。當該窗口被移動時,Windows使用被保存的位圖來重建屏幕圖象
在EasyWin應用程序中,是按如下方式對WNDCLASS結構進行填充和注冊的:
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "EasyWin";
可以看到,wc.style被設為CS_VREDRAW | CS_HREDRAW,表示只要窗口的高度或寬度發生變化,都會重畫整個窗口。
第二個成員lpfnWndProc的值為(WNDPROC)WinProc。表明該窗口類的消息處理函數是WinProc()函數。這裡,要指定窗口的消息處理函數的遠指針,輸入消息處理函數的函數名稱即可,必要時應該進行強制類型轉換,將其轉換成WNDPROC型。
接下來的cbClsExtra和wc.cbWndExtra在大多數情況下都會設為0。
然後的hInstance成員,給它的值是由WinMain()傳來的應用程序的實例句柄,表明該窗口與該實例是相關聯的。事實上,只要是注冊窗口類,該成員的值始終是該程序的實例句柄,你應該象背書一樣記住它。
下面的hIcon,是讓你給這個窗口指定一個圖標,調用 LoadIcon( hInstance, IDI_APPLICATION ),可以調用系統內部預先定義好的標志符為IDC_APPLICATION的圖標作為該窗口的圖標。
同樣,調用LoadCursor( NULL, IDC_ARROW )為該窗口調用系統內部預先定義好的箭頭型鼠標。
hbrBackground成員用來定義窗口的背景畫刷顏色,也就是該窗口的背景色。調用GetStockObject(WHITE_BRUSH)可以獲得系統內部預先定義好的白色畫刷作為窗口的背景色。
上面的LoadIcon()、LoadCursor()、GetStockObject()都是Windows的API函數,它們的用法可以參看VC的幫助,這裡就不多介紹了。
lpszMenuName成員的值我們給它NULL,表示該窗口將沒有菜單。如果你想讓你的窗口擁有菜單,就把lpszMenuName成員賦值為標志菜單資源的字符串。
WNDCLASS結構的最後一個成員lpszClassName是讓你給這個窗口類起一個唯一的名稱,因為Windows操作系統中有許許多多的窗口類,必須用一個獨一無二的名稱來代表它們。通常,你可以用你的程序名來命名這個窗口類的名稱。這個名稱將在創建窗口的CreateWindow()函數中用到。
填充完畢後,對於WNDCLASS結構,調用RegisterClass()函數進行注冊;對於WNDCLASSEX結構,調用RegisterClassEx()函數進行注冊,它們的原型分別如下:
ATOM RegisterClass( CONST WNDCLASS *lpWndClass );
ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx );
該函數如調用成功,則返回一個非0值,表明系統中已經注冊了一個名為EasyWin的窗口類。如果失敗,則返回0。
創建窗口
當窗口類注冊完畢之後,並不會有窗口顯示出來,因為注冊的過程僅僅是為創建窗口所做的准備工作。實際創建一個窗口的是通過調用CreateWindow()函數完成的。窗口類中已經預先定義了窗口的一般屬性,而CreateWindow()中的參數可以進一步指定一個窗口的更具體的屬性,在EasyWin程序中,是如下調用CreateWindow()函數來創建窗口的:
hwnd = CreateWindow(
"EasyWin", //創建窗口所用的窗口類的名稱*
"一個基本的Win32程序", //窗口標題
WS_OVERLAPPEDWINDOW, //窗口風格,定義為普通型*
100, //窗口位置的x坐標
100, //窗口位置的y坐標
400, //窗口的寬度
300, //窗口的高度
NULL, //父窗口句柄
NULL, //菜單句柄
hInstance, //應用程序實例句柄*
NULL ); //一般都為NULL
CreateWindow()函數的參數的含義在上面的注釋中已有介紹,注釋後打了星號標記的參數應該著重注意,其它的參數都很簡單,不多做介紹,可參看VC的幫助。
第一個參數是創建該窗口所使用的窗口類的名稱,注意這個名稱應與前面所注冊的窗口類的名稱一致。
第三個參數為創建的窗口的風格,下表列出了常用的窗口風格:
風格 含義 WS_OVERLAPPEDWINDOW 創建一個層疊式窗口,有邊框、標題欄、系統菜單、最大最小化按鈕,是以下幾種風格的集合:WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX WS_POPUPWINDOW 創建一個彈出式窗口,是以下幾種風格的集合: WS_BORDER,WS_POPUP,WS_SYSMENU。WS_CAPTION與WS_POPUPWINDOW風格必須一起使用才能使窗口菜單可見
idth="23%">WS_OVERLAPPED 創建一個層疊式窗口,它有標題欄和邊框,與WS_TILED風格一樣 WS_POPUP 該窗口為彈出式窗口,不能與WS_CHILD同時使用 WS_BORDER 窗口有單線邊框 WS_CAPTION 窗口有標題欄 WS_CHILD 該窗口為子窗口,不能與WS_POPUP同時使用 WS_DISABLED 該窗口為無效,即對用戶操作不產生任何反應 WS_HSCROLL 窗口有水平滾動條 WS_ICONIC 窗口初始化為最小化 WS_MAXIMIZE 窗口初始化為最大化 WS_MAXIMIZEBOX 窗口有最大化按鈕 WS_MINIMIZE 與WS_MAXIMIZE一樣 WS_MINIMIZEBOX 窗口有最小化按鈕 WS_SIZEBOX 邊框可進行大小控制的窗口 WS_SYSMENU 創建一個有系統菜單的窗口,必須與WS_CAPTION風格同時使用 WS_THICKFRAME 創建一個大小可控制的窗口,與WS_SIZEBOX 風格一樣. WS_TILED 創建一個層疊式窗口,有標題欄 WS_VISIBLE 窗口為可見 WS_VSCROLL 窗口有垂直滾動條
程序中使用了WS_OVERLAPPEDWINDOW標志,它是創建一個普通窗口常用的標志。而在DirectX編程中,我們常用的是WS_POPUP,用這個標志創建的窗口沒有標題欄和系統菜單,如果設定窗口為最大化,客戶區可以占滿整個屏幕,以滿足DirectX編程的需要。
CreateWindow()函數後面的參數中,仍用到了該應用程序的實例句柄hInstance。
如果窗口創建成功,返回值是新窗口的句柄,否則返回NULL。
顯示和更新窗口 窗口創建後,並不會在屏幕上顯示出來,要真正把窗口顯示在屏幕上,還得使用ShowWindow()函數,其原型如下:
BOOL ShowWindow( HWND hWnd, int nCmdShow );
參數hWnd指定要顯示得窗口的句柄,nCmdShow表示窗口的顯示方式,這裡指定為從WinMain()函數的nCmdShow所傳遞而來的值。
由於ShowWindow()函數的執行優先級不高,所以當系統正忙著執行其它的任務時,窗口不會立即顯示出來,此時,調用UpdateWindow()函數以可以立即顯示窗口。其函數原型如下:
BOOL UpdateWindow( HWND hWnd );
消息循環 在Win32編程中,消息循環是相當重要的一個概念,看似很難,但是使用起來卻是非常簡單。在WinMain()函數中,調用InitWindow()函數成功的創建了應用程序主窗口之後,就要啟動消息循環,其代碼如下:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Windows應用程序可以接收以各種形式輸入的信息,這包括鍵盤、鼠標動作 、記時器產生的消息,也可以是其它應用程序發來的消息等等。Windows系統自動監控所有的輸入設備,並將其消息放入該應用程序的消息隊列中。
GetMessage()函數則是用來從應用程序的消息隊列中按照先進先出的原則將這些消息一個個的取出來,放進一個MSG結構中去。GetMessage()函數原型如下:
BOOL GetMessage(
LPMSG lpMsg, //指向一個MSG結構的指針,用來保存消息
HWND hWnd, //指定哪個窗口的消息將被獲取
UINT wMsgFilterMin, //指定獲取的主消息值的最小值
UINT wMsgFilterMax //指定獲取的主消息值的最大值
);
GetMessage()將獲取的消息復制到一個MSG結構中。如果隊列中沒有任何消息,GetMessage()函數將一直空閒直到隊列中又有消息時再返回。如果隊列中已有消息,它將取出一個後返回。MSG結構包含了一條Windows消息的完整信息,其定義如下:
typedef struct tagMSG {
HWND hwnd; //接收消息的窗口句柄
UINT message; //主消息值
WPARAM wParam; //副消息值,其具體含義依賴於主消息值
LPARAM lParam; //副消息值,
其具體含義依賴於主消息值
DWord time; //消息被投遞的時間
POINT pt; //鼠標的位置
} MSG;
該結構中的主消息表明了消息的類型,例如是鍵盤消息還是鼠標消息等,副消息的含義則依賴於主消息值,例如:如果主消息是鍵盤消息,那麼副消息中則存儲了是鍵盤的哪個具體鍵的信息。
GetMessage()函數還可以過濾消息,它的第二個參數是用來指定從哪個窗口的消息隊列中獲取消息,其它窗口的消息將被過濾掉。如果該參數為NULL,則GetMessage()從該應用程序線程的所有窗口的消息隊列中獲取消息。
第三個和第四個參數是用來過濾MSG結構中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息將被過濾掉。如果這兩個參數為0,則表示接收所有消息。
當且僅當GetMessage()函數在獲取到WM_QUIT消息後,將返回0值,於是程序退出消息循環。
TranslateMessage()函數的作用是把虛擬鍵消息轉換到字符消息,以滿足鍵盤輸入的需要。DispatchMessage()函數所完成的工作是把當前的消息發送到對應的窗口過程中去。
開啟消息循環其實是很簡單的一個步驟,幾乎所有的程序都是按照EasyWin的這個方法。你完全不必去深究這些函數的作用,只是簡單的照抄就可以了。
消息處理函數 消息處理函數又叫窗口過程,在這個函數中,不同的消息將用switch語句分配到不同的處理程序中去。Windows的消息處理函數都有一個確定的樣式,即這種函數的參數個數和類型以及其返回值的類型都有明確的規定。在VC的說明書中,消息處理函數的原型是這樣定義的:
LRESULT CALLBACK WindowProc(
HWND hwnd, //接收消息窗口的句柄
UINT uMsg, //主消息值
WPARAM wParam, //副消息值
LPARAM lParam //副消息值
);
如果你的程序中還有其它的消息處理函數,也都必須按照上面的這個樣式來定義,但函數名稱可以隨便取。EasyWin中的WinProc()函數就是這樣一個典型的消息處理函數。
消息處理函數的四個參數是由GetMessage()函數從消息隊列中獲得MSG結構,然後分解後得到的。第二個參數uMsg和MSG結構中的message值是一致的,代表了主消息值。程序中用switch語句來將不同類型的消息分配到不同的處理程序中去。
WinProc()函數明確的處理了4個消息,分別是WM_KEYDOWN(擊鍵消息)、WM_RBUTTONDOWN(鼠標右鍵按下消息)、WM_PAINT(窗口重畫消息)、WM_DESTROY(銷毀窗口消息)。
值得注意的是,應用程序發送到窗口的消息遠遠不止以上這幾條,象WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE等這樣頻頻使用的消息就有幾十條。為了減輕編程的負擔,Windows的API提供了DefWindowProc()函數來處理這些最常用的消息,調用了這個函數後,這些消息將按照系統默認的方式得到處理。
因此,在switch_case語句中,只須明確的處理那些有必要進行特別響應的消息,把其余的消息交給DefWindowProc()函數來處理,是一種明智的選擇,也是你必須做的一件事。
結束消息循環 當用戶按Alt+F4或單擊窗口右上角的退出按鈕,系統就向應用程序發送一條WM_DESTROY的消息。在處理此消息時,調用了PostQuitMessage()函數,該函數會給窗口的消息隊列中發送一條WM_QUIT的消息。在消息循環中,GetMessage()函數一旦檢索到這條消息,就會返回FALSE,從而結束消息循環,隨後,程序也結束。
小結 本章介紹的是Win32編程的基礎知識,在進行DirectX編程之前,掌握它們是十分必要的。
通過本文的學習,你應該學到以下知識:
如何創建一個Win32應用程序工程
用RegisterClass()函數注冊一個窗口類,
再立即調用CreateWindow()函數創建一個窗口的實例
設置窗口的類型以及將一個消息處理函數與窗口聯系上
用一固定的模式開啟消息循環
了解消息處理函數的定義規則,如何自己定義一個窗口消息處理函數
在消息處理函數中,最後必須調用DefWindowProc()函數以處理那些缺省的消息
調用PostQuitMessage()函數以結束消息循環