標籤:
windows視窗編程(通常意義上的win32)有幾個比較核心的概念:入口函數WinMain、視窗類別Window Class、視窗過程、訊息處理機制、通用控制項。本文主要介紹視窗類別的相關概念,包括:
- 視窗類別的類型;
- 視窗類別的註冊及使用;
- 視窗類別的構成。
視窗類別是基於進程的,每個應用程式在建立視窗之前必須註冊視窗類別(或者使用作業系統定義的視窗類別),使用完成之後需要銷毀(反註冊)。
介紹視窗類別的主要目的在於明確windows視窗編程的相關概念,掌握windows內部對於GUI處理的機制。如果你在用mfc或者其他介面架構,本文是沒有必要閱讀的。
一、視窗類別的類型
windows下視窗類別分為三種:
- 系統視窗類別
- 應用程式全域視窗類別
- 應用程式局部視窗類別
三者主要區別在於範圍、註冊銷毀的時刻及方式上。
1. 系統視窗類別
顧名思義,系統視窗類別是有作業系統註冊的。一些系統視窗類別是所有進程都可以訪問的,一些系統視窗類別是只能被作業系統內部使用的。對於系統視窗類別,應用程式是不能銷毀的。
作業系統在應用程式首次調用GUI函數時,為當前進程註冊系統視窗類別。也就是說每個獨立的應用程式都會用於一份同樣的系統視窗類別註冊。下面表格中給出了任意進程都可以使用的系統視窗類別名稱。
視窗類別 |
描述資訊 |
Button |
按鈕的視窗類別名稱。 |
ComboBox |
組合框的視窗類別名稱。 |
Edit |
編輯框控制項的視窗類別名稱。 |
ListBox |
列表框的視窗類別名稱。 |
MDIClient |
MDI子視窗的視窗類別名稱。 |
ScrollBar |
捲軸的視窗類別名稱。 |
Static |
靜態控制項的視窗類別名稱。 |
下表是僅由作業系統內部使用的視窗類別名稱。
視窗類別 |
描述資訊 |
ComboLBox |
組合列表框的視窗類別名稱。 The class for the list box contained in a combo box. |
DDEMLEvent |
動態資料交換管理庫(DDEML)事件的視窗類別名稱。 The class for Dynamic Data Exchange Management Library (DDEML) events. |
Message |
The class for a message-only window. |
#32768 |
菜單的視窗類別名稱。 |
#32769 |
桌面視窗的視窗類別名稱。 |
#32770 |
對話方塊的視窗類別名稱。 |
#32771 |
工作列切換視窗的視窗類別名稱。 |
#32772 |
表徵圖標題列的視窗類別名稱。The class for icon titles. |
2. 應用程式全域視窗類別
應用程式全域視窗類別指的是由可執行程式或者DLL註冊的,可以被當前進程其他模組使用的視窗類別。比如你在某個DLL中註冊一個全域視窗類別,應用程式可以通過載入該DLL之後就可以使用對應的視窗類別。
全域視窗類別在不使用時必須由使用者自行銷毀(使用 UnregisterClass)。
3. 應用程式局部視窗類別
應用程式局部視窗類別指的是由可執行程式或者DLL註冊的,僅用於當前模組的視窗類別。我們可以註冊很多局部視窗類別,但推薦的做法是僅註冊一個視窗類別,用於建立應用程式主視窗。
作業系統在應用程式退出時自動銷毀局部視窗類別,我們也可通過 UnregisterClass函數手動銷毀局部視窗類別。
二、視窗類別的註冊及使用
視窗類別定義了windows下的視窗屬性,包括視窗樣式、表徵圖、游標、功能表項目、視窗過程等。要註冊視窗類別,首先需要填充WNDCLASS 、WNDCLASSEX結構,然後使用設定好的參數調用RegisterClass、RegisterClassEx函數(二者主要區別在於RegisterClass函數不支援小表徵圖設定,在通常使用中可以統一使用RegisterClassEx函數)。
如果需要註冊應用程式全域視窗類別,需要設定WNDCLASSEX結構的style屬性為 CS_GLOBALCLASS;註冊局部視窗類別請勿指定 CS_GLOBALCLASS屬性。
視窗類別主要用於CreateWindow、CreateWindowEx函數的調用(第一個參數lpClassName),原型如下:
HWND WINAPI CreateWindow( _In_opt_ LPCTSTR lpClassName, _In_opt_ LPCTSTR lpWindowName, _In_ DWORD dwStyle, _In_ int x, _In_ int y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam);HWND WINAPI CreateWindowEx( _In_ DWORD dwExStyle, _In_opt_ LPCTSTR lpClassName, _In_opt_ LPCTSTR lpWindowName, _In_ DWORD dwStyle, _In_ int x, _In_ int y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam);
視窗類別是按照字串進行尋找匹配的。
1. 系統如何尋找視窗類別
作業系統會維護一個按照三種類型分類的視窗類別列表,在應用程式需要建立視窗的時候,按照下列順序搜尋錨定視窗類:
- 使用視窗類別名稱及當前模組執行個體控制代碼尋找應用程式局部視窗類別列表(注意當前模組執行個體控制代碼主要為了區分不同模組註冊的同名視窗類別);
- 若局部視窗類別列表中未找到,則繼續尋找應用程式全域視窗類別列表;
- 若全域視窗類別列表中未找到,則超找系統視窗類別列表。
所有的視窗建立都會遵循上面的尋找順序。因此這樣也提供了重寫系統視窗類別的一種方法,在應用中註冊和系統視窗類別的同名的局部視窗類別,這樣即可以修改當前應用程式中替換某些系統視窗,同時不影響其他應用程式的系統視窗類別使用。
2. 視窗類別的歸屬
視窗類別通常意義可以認為是屬於註冊該類的可執行程式或者DLL。作業系統使用調用RegisterClassEx函數的WNDCLASSEX結構的hInstance 判斷視窗類別的所有權。也就是說DLL在註冊視窗類別的時候必須使用DLL本身的控制代碼。
當動態載入的DLL卸載時,使用DLL註冊視窗類別的視窗可能還會存在。這就需要調用者保證DLL卸載之前,關閉所有引用DLL所註冊視窗類別的視窗,同時使用 UnregisterClass函數銷毀視窗類別。否則可能存在訪問越界等情況。(因為DLL卸載之後,其過程函數地址是無效的。)
三、視窗類別的構成
視窗類別給出了使用該類的windows視窗的一些屬性。主要參數設定位於WNDCLASSEX結構中。其定義如下:
typedef struct tagWNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm;} WNDCLASSEX, *PWNDCLASSEX;
在實際使用時,系統僅要求提供類名稱(lpszClassName)、回呼函數地址(lpfnWndProc)、執行個體控制代碼(hInstance)三個參數,其他參數用於設定window視窗屬性。下面逐一說明視窗類別的元素構成:
類名稱(欄位:lpszClassName)
用於唯一標識視窗類別。視窗類別是進程相關的,使用時必須保證視窗類別的類名稱在當前進程中是唯一的。
另外由於類名稱佔用系統私人元表格(system‘s private atom table),註冊時請保證類名儘可能短。
可以使用 GetClassName函數擷取當前視窗的視窗類別名稱。
視窗過程地址(欄位:lpfnWndProc)
系統用該地址回調windows所有的訊息。具體看參考Window Procedures。原型必須符合下面定義:
LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
執行個體控制代碼(欄位:
hInstance)
視窗類別需要使用執行個體控制代碼來標識其歸屬的可執行程式或DLL。系統在啟動可執行程式或DLL時都會給其設定執行個體控制代碼,用於管理各模組,該執行個體控制代碼可在可執行模組的入口函數中擷取(比如在WinMain或DllMain中)。
類游標(欄位:hCursor)
用於指定滑鼠在客戶區顯示的樣式。可使用 LoadCursor函數載入一個游標檔案,用於設定該欄位。
可使用 SetCursor函數設定游標屬性。其他詳細資料可參考Cursors。
類表徵圖(欄位:hIcon和
hIconSm)
類表徵圖是一張圖片,用於標識特殊的視窗類別。分為大表徵圖和小表徵圖。大表徵圖用於視窗切換(ALT+Tab)以及大表徵圖視圖下的工作列和案頭瀏覽器(explorer.exe)。小表徵圖用於顯示在標題列、小表徵圖視圖下的工作列和案頭瀏覽器。
表徵圖實際大小可通過 GetSystemMetrics函數擷取。設定欄位SM_CXICON和SM_CYICON可擷取大表徵圖的長寬,設定欄位 SM_CXSMICON和SM_CYSMICON可擷取小表徵圖的長寬。
類背景畫刷(欄位:
hbrBackground)
用於設定視窗客戶區重繪的畫刷,詳細設定可參考WM_ERASEBKGND訊息。使用可以建立自訂畫刷,也可使用系統畫刷(GetSysColorBrush)。
類菜單(欄位:hMenu)
用於設定系統預設菜單。可使用菜單名稱,也可以使用 MAKEINTRESOURCE 。詳細設定建議參考Menus。
視窗類別樣式(欄位:
style)
指定視窗類別建立的一些預設參數,可參考Window Class Styles。
附加視窗類別儲存空間(欄位:
cbClsExtra)
所有視窗共用的唯一的類儲存空間,類似於c++中類的靜態成員函數,作業系統預設儲存在WNDCLASSEX後面,如果不需要,該欄位必須設定為0。
附加類儲存空間是分配在系統本地堆(local heap)中的,建議欄位長度不要太大(不超過40個位元組)。可使用 SetClassWord、SetClassLong函數設定對應欄位,使用GetClassWord、GetClassLong函數擷取相應參數。
很多經典的win32編程資料可能會提到這個欄位,目前多數應用是不需要設定這個參數的。也不推薦使用。
附加視窗儲存空間(欄位:
cbWndExtra)
概念跟附件視窗類別儲存空間類似,只是附件視窗儲存空間是按照每個視窗分配的,類似於c++中的成員變數,每個執行個體有一個。附件視窗儲存空間一般用於儲存視窗相關資料。
可使用 SetWindowLong和 GetWindowLong函數來擷取、設定附件視窗儲存空間中的資料。
四、總結
windows編程的核心在於訊息處理機制,而視窗類別作為一個獨立的抽象單元,為我們提供了註冊視窗類別並建立的方式,有些內容是值得借鑒和學習的。雖然概念比較老,但是如果想深入瞭解win32內部的處理方式,還是需要在理解的基礎上深化下。
windows視窗類別的構成有很多參數,如果有一些預設參數無法確認,建議查看msdn上對應的內容,一般都會有描述的。
視窗類別(Window Class)概述