標籤:
Windows程式內部運行機制
一、 API與SDK
Windows作業系統提供了各種各樣的函數,以方便我們開發Windows應用程式,這些函數是Windows作業系統提供給應用程式編程的介面(Application Programming Interface),簡稱為API函數。我們在編寫Windows程式時所說的API函數,就是指系統提供的函數,所有主要的Windows函數都在Windows.h標頭檔中進行了說明。
SDK的全稱是Software Development Kit,中文譯為軟體開發包。SDK實際上就是開發所需資源的一個集合,包括API函數庫、協助文檔、使用手冊、協助工具輔助等資源。
二、 視窗與控制代碼
視窗是Windows應用程式中一個非常重要的元素,一個Windows應用程式至少要有一個視窗,稱為主視窗。視窗是螢幕上的一塊矩形地區,是Windows應用程式與使用者進行互動的介面。
一個應用程式視窗通常包括標題列、功能表列、系統功能表、最小化框、最大化框、可調邊框,有的還有捲軸。
視窗可以分為客戶區和非客戶區。客戶區是視窗的一部分,應用程式通常在客戶區中顯示文字或者繪製圖形。標題列、功能表列、系統功能表、最小化框、最大化框、可調邊框統稱為視窗的非客戶區,它們由Windows系統來管理,而應用程式則主要管理客戶區的外觀及操作。
在Windows應用程式中,視窗是通過視窗控制代碼(HWND)來標識的。我們要對某個視窗進行操作,首先就要得到這個視窗的控制代碼。在Windows程式中,有各種各樣的資源(視窗,表徵圖、游標等),系統在建立這些資源時會為它們分配記憶體,並返回標識這些資源的標識號,即控制代碼。
三、 訊息與訊息佇列
在Windows中,不僅使用者程式可以調用系統的API函數,反過來,系統也會調用使用者程式,這個調用是通過訊息佇列來進行的
Windows程式設計是一種基於事件驅動方式的程式設計模式,主要是基於訊息的。使用者與應用程式互動時,作業系統感知事件,將事件封裝成一個訊息,投遞到應用程式的訊息佇列中,然後應用程式從訊息佇列中取出訊息並進行響應。在這個處理過程中,作業系統也會給應用程式“發送訊息”。所謂“發送訊息”,實際上是作業系統調用程式中一個專門負責處理訊息的函數,這個函數稱為視窗過程。
- 訊息
在Windows程式中,訊息是由MSG結構體定義的。
Windows將訊息對應的數值定義為WM_XXX宏的形式,WM是Windows Message的縮寫,XXX對應某種訊息的英文拼字的大寫形式。在程式中,我們通常都是以WM_XXX宏的形式來使用訊息的。
2. 訊息佇列
每一個Windows應用程式開始執行後,系統都會為該程式建立一個訊息佇列,這個訊息佇列用來存放該程式建立的視窗的訊息。Windows將產生的訊息依次放到訊息佇列中,而應用程式則通過一個訊息迴圈不斷地從訊息佇列中取出訊息,並進行響應。這種訊息機制,就是Windows程式啟動並執行機制。
3. 進隊訊息和不進隊訊息
Windows程式中的訊息可以分為“進隊訊息”和“不進隊訊息”。進隊的訊息將由系統放入到應用程式的訊息佇列中,然後由應用程式取出並發送。不進隊的訊息在系統調用視窗過程時,直接發送給視窗。不管是進隊訊息還是不進隊訊息,最終都由系統調用視窗過程函數對訊息進行處理。
4. WinMain函數
當Windows作業系統啟動一個程式時,調用該程式的WinMain函數(實際是由插入到可執行檔中的啟動代碼調用的)。WinMain是Windows程式的進入點函數,與DOC程式的進入點函數main的作用相同,當WinMain函數結束或返回時,Windows應用程式結束。
四、 編寫一個完整的Win32程式
步驟:
WinMain函數的定義
建立一個視窗
進行訊息迴圈
編寫視窗過程函數
1. 視窗的建立
建立一個完整的視窗,需要經過下面幾個操作步驟:
設計一個視窗類別 WNDCLASS
註冊視窗類別 RegisterClass
建立視窗 CreateWindow
顯示及更新視窗 ShowWindow UpdateWindow
一個Windows程式可以包含多個視窗過程函數,一個視窗過程總是與某一特定的視窗類別相關聯,基於該視窗類別建立的視窗使用同一個視窗過程。
如果視窗建立成功,CreateWindow函數將返回系統為該視窗分配的控制代碼,否則返回NULL。注意,在建立視窗之前應先定義一個視窗控制代碼變數來接受建立視窗之後返回的控制代碼值。
UpdateWindow函數通過發送一個WM_PAINT訊息來重新整理視窗,UpdateWindow將WM_PAINT訊息直接發送給了視窗過程函數進行處理,而沒有放到訊息佇列中,請注意這一點。
2. 訊息迴圈
在建立視窗、顯示視窗、更新視窗後,需要編寫一個訊息迴圈,不斷地從訊息佇列中取出訊息,並進行響應。要從訊息佇列中取出訊息,需要調用GetMessage()函數。
參數hWnd指定接收屬於哪一個視窗的訊息。通常將其設定為NULL,用於接收屬於調用線程的所有視窗的視窗訊息。
參數wMsgFilterMin指定要擷取的訊息的最小值,通常設定為0。
參數wMsgFilterMax指定要擷取的訊息的最大值。如果wMsgFilterMin和wMsgFilterMax都設定為0,則接受所有訊息。
GetMessage函數只有在接收到WM_QIUT訊息時,才返回0。此時,while語句判斷的條件為假,迴圈退出,程式才有可能結束運行。在沒有接收到WM_QIUT訊息時,Windows應用程式就通過這個while迴圈來保證程式始終處於運行狀態
五、 知識點
1. 建立一個Win32應用程式的步驟:
編寫WinMain函數,可以在MSDN上尋找並複製
設計視窗類別
註冊視窗類別
顯示並更新視窗
編寫訊息迴圈
編寫視窗過程函數
2. 在Windows.h中,以CS_開頭的類樣式(Class Style)標識符被定義為16位的類常量,這些常量都只有某一位為1。用這種方式定義的標識符稱為“位標誌”,我們可以使用位元運算操作符來組合使用這些樣式。
3. 回呼函數的實現機制是:
(1) 定義一個回呼函數;
(2) 提供函數實現的一方在初始化的時候,將回呼函數的函數指標註冊給調用者;
(3) 當特定的事件或條件發生的時候,調用者使用函數指標調用回呼函數對事件進行處理。
4. 針對Windows的訊息處理機制,視窗過程函數被調用的過程如下:
(1) 在設計視窗類別的時候,將視窗過程函數的地址付給lpfnWndProc成員變數;
(2) 調用RegisterClass(&wndclass)註冊視窗類別,那麼系統就有了我們所編寫的視窗過程函數的地址;
(3) 當應用程式接收到某一視窗的訊息時,調用DispatchMessage(&msg)將訊息回傳給系統,系統則利用先前註冊視窗類別時得到的函數指標,調用視窗過程函數對訊息進行處理。
5. 在函數調用過程中,會使用棧。_stdcall與_cdecl是兩種不同的函數呼叫慣例,定義了函數參數入棧的順序,由調用函數還是被調用函數將參數彈出棧,以及產生函數修飾名的方法。
6. 在VC++中,對於自訂的菜單、表徵圖、游標、對話方塊等資源,都儲存在資源指令碼(通常副檔名為.rc)檔案中。資源檔本身是文字檔格式,如果瞭解資源檔的編寫格式,也可以直接使用文字編輯器對資源進行編輯
7. 在VC++中,資源是通過標識符(ID)來標識的,同一個ID可以標識多個不同的資源。資源的ID實質上是一個整數,在“resource.h”中定義為一個宏。
8. GetStockObject函數不僅可以用於擷取畫刷控制代碼,還可以用於擷取畫筆、字型和調色盤的控制代碼。
9. 要注意,菜單並不是一個視窗。
10. Windows應用程式的訊息處理機制:
(1) 作業系統接收到應用程式的視窗訊息,將訊息投遞到該應用程式的訊息佇列中。
(2) 應用程式在訊息迴圈中調用GetMessage函數從訊息佇列中取出一條一條的訊息。取出訊息後,應用程式可以對訊息進行一些預先處理。
(3) 應用程式調用DispatchMessage,將訊息回傳給作業系統。訊息由MSG結構體對象來表示,其中包含了接收訊息的視窗的控制代碼。因此,DispatchMessage函數總能進行正確的傳遞。
(4) 系統利用WNDCLASS結構體的lpfnWndProc成員儲存的視窗過程函數的指標調用視窗過程,對訊息進行處理(即“系統給應用程式發送了訊息”)。
11. 要注意區分WNDCLASS中的style成員和CreateWindow函數的dwStyle參數,前者指定視窗類別的樣式,基於該視窗類別建立的視窗都有這些格式,後者是指定某個具體的視窗的樣式。下面是幾種常用的視窗類別型的說明:
WS_OVERLAPPED:產生一個層疊的視窗,一個層疊的視窗有一個標題列和一個邊框;
WS_CAPTION:建立一個有標題列的窗
WS_SYSMENU:建立一個在標題列上帶有系統功能表的視窗
WS_THICKFRAME:建立一個具有可調邊框的視窗
12. TranslateMessage函數用於將虛擬鍵訊息轉換為字元訊息。字元訊息被投遞到調用線程的訊息佇列中,當下一次調用GetMessage函數時被取出。注意,TranslateMessage函數並不會修改原有的訊息,它只是產生新的訊息並投遞到訊息佇列中。
13. DispatchMessage函數指派一個訊息到視窗過程,由視窗過程對訊息進行處理。DispatchMessage實際上是將訊息回傳給作業系統,由作業系統調用視窗過程對訊息進行處理。
14. 訊息擷取與發送的函數對比:
PeekMessage會立即返回 可以將訊息儲存在訊息佇列中 。
GetMessage在有訊息時返回 會將訊息從訊息佇列中移除。
PostMessage:把訊息放到指定視窗所在的線程訊息佇列中後立即返回。
PostThreadMessage:把訊息放到指定線程的訊息佇列中後立即返回。對於線程訊息,應該將MSG結構體的hwnd成員設為NULL。
SendMessage:直接把訊息送到視窗過程處理, 處理完了才返回。
15. DC(Device context) 裝置描述表,裝置上下文。
包含了裝置資訊的結構體,在Windows平台下所有的圖形相關操作都利用DC來完成。GetDC(),ReleaseDC()成對使用,防止記憶體流失。
16. WM_PAINT
WM_PAINT 訊息是Windows系統產生的,用於通知應用程式重繪其視窗的訊息。通常情況下Windows程式第一次產生WM_PAINT訊息是由即將進入訊息迴圈之前的UpdateWindow函數發出的。Windows利用這個機會,繪製視窗程序的視窗。
WM_PAINT訊息產生的幾種情況:
視窗大小改變(取決於CS_HREDRAW,CS_VREDRAW的設定)
視窗從無到有
視窗最小化後恢複
被其他視窗覆蓋後再顯示。
使用BeginPaint()與EndPaint()擷取DC,同樣的BeginPaint()與EndPaint()這兩個Parint只能在WM_PAINT訊息中調用。
Windows程式內部運行機制