要想熟練掌握Windows應用程序的開發,首先需要理解Windows平台下程序運行的內部機制,然而在.NET平台下,創建一個Windows桌面程序,只需要簡單地選擇Windows窗體應用程序就可以了,微軟幫我們做了非常好的封裝,以至於對於很多.NET開發人員至今也不清楚Windows 平台下程序運行的內部機制,所以本專題將深入剖析下Windows 程序的內部運行機制。
有朋友會問,理解了程序運行的內部機制有什麼用,因為在我們實際開發中用得微軟提供的模板來進行編程?對於這個疑問,我的回答是——理解了Windows平台下程序的運行內部機制可以使我們更有自信地寫代碼,因為我們知道模板後台幫我們封裝的內容,並且理解這點也是打好了基礎,基礎打好了,學習新的知識也就快了。
窗口是Windows應用程序中非常重要的一個元素,一個Windows應用程序至少要有一個窗口,稱為主窗口,窗口是我們看到的一塊矩形區域,它是與用戶進行交互的接口,利用窗口可以接受用戶的輸入以及對用戶輸入的響應。例如我們看到的QQ登陸界面就是一個窗口。窗口又可分為客戶區和非客戶區,如下圖所示,其中,客戶區通常用來顯示控件或文字,標題欄、系統菜單,菜單欄、最小化框和最大化框、可調邊框都稱為窗口的非客戶區,它們主要由Windows系統進行管理,我們創建的應用程序主要負責客戶區的外觀顯示和操作,窗口也可以有一個父窗口,並且,對話框和消息框都是屬於窗口。
在Windows應用程序中,句柄是用來唯一標識窗口的,我們想對某個窗體進行操作時,必須首先獲得該窗口的句柄,句柄還包括圖標句柄(如上圖中Form1前小圖標),光標句柄(即移到窗體時顯示的光標),和畫刷句柄(上圖中客戶區中顏色就是通過指定窗口類的背景畫刷句柄進行設置的,關於窗口類的結構會在下面介紹)。對於句柄的理解,大家簡單理解為用來標識窗體,它的類型為struct,這點可以通過在VS中通過F12查看HWND的定義。
Windows 操作系統是基於事件驅動的一種操作系統,所以在Windows平台下所有應用程序也是基於事件驅動機制,即是基於消息的。例如,當用戶在窗口中按下鼠標左鍵時,操作系統會知曉這一事件,於是將事件封裝成一個消息,傳遞到應用程序的消息隊列中,,然後應用程序從消息隊列中取出消息並進行響應。在這個處理過程中,操作系統會調用應用程序中專門負責消息處理的函數,該函數稱為窗口過程。
1. 消息
消息是由MSG結構體表示的,MSG的結構體定義如下(也可以參考MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644958(v=vs.85).aspx):
// MSG
typedef *PMSG, *LPMSG;
該結構體中各參數的含義如下:
2. 消息隊列
每一個Windows應用程序開始執行後,系統都會為該程序創建一個消息隊列(從而得出消息隊列是由系統創建的)來存放該程序創建過程中的窗口消息。當用戶在窗口中發送一個消息時,系統會將該消息推送到消息隊列中,而應用程序的過程函數則通過一個消息循環不斷地從消息隊列中取出消息,並進行響應。這種消息機制,就是Windows程序運行的機制。
在Windows程序中,消息又可分為“進隊消息”和“不進隊消息”。進隊的消息由系統放入到應用程序的消息隊列中,然後由應用程序取出並發送給窗口過程處理。不進隊的消息由系統直接調用窗口過程進行處理。
下面,讓我們手動要完成一個完整的Win32桌面程序,該程序實現的功能就是簡單地創建一個窗體,並在窗體中響應鍵盤及鼠標消息,實現該程序的步驟可分為:
當Windows啟動一個桌面程序時,它調用的就是該程序的WinMain函數,該函數是Windows桌面程序的入口函數,與控制台中的main函數的作用相同。WinMain函數的聲明如下所示(也可以參考MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ms633559(v=vs.85).aspx):
上面我列出的定義與MSDN略有不同,MSDN中定義使用的是CALLBACK,而我上面列出的是WINAPI,其實兩者都是一樣的,可以在VS中通過F12查看宏的定義可以發現:
CALLBACK __stdcall WINAPI __stdcall
它們都是代表_stdcall,_stdcall是一種函數調用方式,__stdcall 調用約定用來調用 Win32 API 函數,更多介紹可以參考MSDN:http://msdn.microsoft.com/zh-cn/library/zxk0tw93(v=vs.120).aspx。
WinMain函數的4個參數是由系統調用WinMain函數時,傳遞給應用程序應用程序的,它們具體的含義為:
創建一個完整的窗口,需要經過下面4個步驟:
上面四個步驟,仔細想想你也知道的,我們想創建一個窗口,首先應該設計下它長什麼樣子吧(第一步),設計完成之後,總要讓系統知道已經設計完了窗口了吧,所以我們要通過注冊窗口類的方式來通知系統(第二步),成功注冊之後,系統已經知道存在這樣一個窗口了,接下來就應該創建窗口類的一個實例了(第三步),最後就是把創建完的窗口顯示和再加修飾下(第四步)。這四步完全來源我們生活,例如,上司找你做一個東西出來,你首先要在腦海中構想它的樣子(設計,第一步),設計完之後,要讓老板知道你設計完成了就應該告知老板你設計完了(第二步),老板知道之後,老板覺得可以就命令工廠把模型做出來(第三步),最後就是拿給客戶看(第四步)。下面我們按照這4步來完成一個窗口的創建。
Windows已經為我們定義好了一個窗口類,它的具體定義如下,你也可以自我查看MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ms633576(v=vs.85).aspx。
typedef *PWNDCLASS;
在程序中,我們創建一個窗口類對象,然後為該對象指定其屬性來完成窗口類的設計。
設計完窗口類之後,我們需要使用RegisterClass(CONST WNDCLASS *lpWndClass)函數來完成窗口類的注冊,注冊成功之後,我們才可以創建該類型的窗口。
注冊窗口類之後,即已經告知系統,我們已經存在這樣的一個窗口類,下面可以使用CreateWindow()函數來創建該類型的一個窗口,CreateWindow函數的定義如下:
創建窗口後,最好一步需要做的就是將窗口展示給用戶看,我們可以通過ShowWindow()和UpdateWindow()函數來完成,ShowWindow()函數來設置窗口的特殊狀態,調用完ShowWindow()之後,接下來調用UpdateWindow()函數來刷新窗口。即把設置好的窗口繪制在桌面上。調用UpdateWindow()函數之後將發送一個WM_PAINT消息給窗口過程函數來進行處理,從而來刷新窗口,注意,該WM_PAINT消息是沒有放到前面介紹的消息隊列中,屬於不入隊的消息。
接下面,我們需要實現一個消息循環函數,來完成不斷從消息隊列中取出消息,並交給窗口過程函數進行處理。我們可以通過Windows API 中GetMessage()函數來完成這個過程,下面是該函數的原型:
GetMessage()函數除了接收WM_QUIT消息(接收WM_QUIT消息返回0)外,接收其他函數都返回非零值,如果出現錯誤則返回-1。如參數hWnd為無效句柄時(即傳遞NULL),此時發送錯誤返回為-1.
前面都涉及到對消息的處理,下面就完成最後一步——窗口過程函數的實現,該函數為回調函數,窗口過程函數的聲明如下,(也可以參考MSDNhttp://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx):
有了上面的實現思路之後,那麼實現該程序將再簡單不過了,同時,大家可以根據下面代碼來對比理解下上面介紹的理論,具體實現代碼如下(這裡需要指明一點,如果不小心把回調函數的實現的名字輸入錯誤時,將出現如下圖所示的錯誤):
#include <Windows.h><stdio.h> UINT uMsg, WPARAM wParam, LPARAM lParam HINSTANCE hPrevInstance, LPSTR lpCmdLine, nCmdShow ===(HBRUSH)GetStockObject(GRAY_BRUSH); wndclass.hCursor =LoadCursor(NULL,IDC_CROSS); wndclass.hIcon====L; wndclass.lpszMenuName=NULL; wndclass.style=CS_HREDRAW|CS_VREDRAW; RegisterClass(& =CreateWindow(L,L,WS_OVERLAPPEDWINDOW,,,, ((breturn=GetMessage(&msg,hwnd,,))!=(breturn==- - TranslateMessage(& DispatchMessage(& ,,L,,=,,L,wcslen(L=BeginPaint(hwnd,&ps); TextOut(hDC,,,L,wcslen(L&(IDYES==MessageBox(hwnd,L,L
輸入上面的代碼在VS中,按下Ctrl+F5按鈕運行程序,你將看到下面的窗口(你可以測試在窗口點擊的效果和鍵盤按下效果):
本專題介紹了Windows程序運行的內部機制,了解了本專題的內容,相信對.NET中WinForm應用程序背後實現的原理將不再陌生了。這裡再總結下純手動創建Windows 桌面程序的步驟: