窗口句柄

窗口句柄

在Windows中,句柄是一個系統內部數據結構的引用。例如當你操作一個窗口,或說是一個Delphi窗體時,系統會給你一個該窗口的句柄,系統會通知你:你正在操作142號窗口,就此你的應用程序就能要求系統對142號窗口進行操作——移動窗口、改變窗口大小、把窗口最小化等等。實際上許多Windows API函數把句柄作為它的第一個參數,如GDI(圖形設備介面)句柄、菜單句柄、實例句柄、點陣圖句柄等,不僅僅局限於窗口函數。換句話說,句柄是一種內部代碼,通過它能引用受系統控制的特殊元素,如窗口、點陣圖、圖標、內存塊、游標、字體、菜單等。

案例


獲取窗口句柄
案例說明
本例實現窗口句柄的獲取。
實現過程
Private Declare Function GetWindowLong Lib user32 Alias GetWindowLongA (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib user32 Alias SetWindowLongA (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function SetLayeredWindowAttributes Lib user32 (ByVal hwnd As Long, ByVal crKey As Long, ByVal bAlpha As Byte, ByVal dwFlags As Long) As Long
Private Const WS_EX_LAYERED = H80000
Private Const GWL_EXSTYLE = (-20)
Private Const LWA_ALPHA = H2
Private Sub Form_Activate()
On Error Resume Next
For i = 0 To 150 Step 2.5
SetLayeredWindowAttributes Me.hwnd, 0, i, LWA_ALPHA
DoEvents
Next i
End Sub
Private Sub Form_load()
Dim rtn As Long
rtn = GetWindowLong(Me.hwnd, GWL_EXSTYLE)
rtn = rtn Or WS_EX_LAYERED
SetWindowLong Me.hwnd, GWL_EXSTYLE, rtn
SetLayeredWindowAttributes Me.hwnd, 0, 0, LWA_ALPHA
End Sub

概念


單從概念上講,句柄指一個對象的標識,而指針是一個對象的內存首地址。從實際處理的角度講,即可以把句柄定義為指針,又可以把它定義為同類對象數組索引,這兩種處理方法都有優缺點,至於選用哪種方式,完全應該看實際需要,這可以說是一種程序設計上的技巧。那種單純認為句柄是指針或索引的想法都是機械的、不確切的。
其實,在Windows中類似的處理是很多的、很靈活的。再舉個相似的例子:
我們知道,在Windows中有個函數叫做CallWindowProc。顧名思義,它的作用就是向指定的窗口過程傳遞一個消息。你也許會想,既然我已經有了窗口過程的指針,為什麼我不可以直接通過這個指針調用該函數(這是C語言的內建功能)?事實上,在Win16中確實可以這麼做,因為GetWindowLong返回的確實是該函數的指針。但在Win32下,GetWindowLong返回的並不是該函數的指針,而是一個包含函數指針的數據結構的指針(MSDN上說返回的是一個窗口函數地址或它的句柄,就是指的這種情況)。該數據結構是可變的,但只要你使用CallWindowProc來調用的話是不會出錯的。這裡我們又看到使用句柄處理帶來的好處。(補充說明一點:微軟在這裡之所以這麼處理,是為了解決16位/32位以及ANSI/UNICODE的轉化問題)

解疑


定義

句柄是什麼?
在windows中,句柄是和對象一一對應的32位無符號整數值。對象可以映射到唯
一的句柄,句柄也可以映射到唯一的對象。

用途

為什麼我們需要句柄?
更準確地說,是windows需要句柄。windows需要向程序員提供必要的編程介面
,在這些介面中,允許程序員訪問、創建和銷毀對象。但是,出於封裝地考慮,wi
ndows並不想向程序員返回指針。指針包含了太多的信息。首先指針給出了對象存儲
的確切位置;其次,要操作一個指針,程序員必須知道指針所指對象的內部結構特
征,也即,windows必須向程序員暴露相應的數據結構,而這些數據結構也許是操作
系統想向程序員隱藏的。
如果說COM技術向用戶隱藏了數據,只暴露了介面並只允許按介面定義的方法操
作數據的話,句柄這種方式則允許你按自己的方式直接操作數據,但windows又不向
你直接暴露數據。直接操作數據是程序員需要的,不暴露數據是windows所需要的,
句柄封裝方式實現了各取所需。

映射

句柄如何與對象映射
封裝背後,必須有一個地方可以實現解碼,以實現句柄和對象的相互轉換。在
windows中,存在兩種映射方式:
a. 全等映射。也即,句柄本身就是一個指針。映射在這裡只是類型轉換而已。
這種情況有,進程實例句柄或模塊句柄,以及資源句柄等等。
b. 基於表格的映射。這是對象指針與句柄之間最普通的映射機制。操作系統創
建表格,並保存所有要考慮的對象。需要創建新對象時,要先在表格中找到空入口
,然後把表示對象的數據添入其中。當對象被刪除時,它的數據成員和其在表中的
入口被釋放。

實現

句柄的定義和實現
我們以GDI對象為例進行討論。創建了GDI對象,就會得到該對象的句柄。句柄
的對象可能是HBRUSH、HPEN、HFONT或HDC中的一種,這依賴於你創建 的GDI對象類
型。但是最普通的GDI對象類型是HGDIOBJ。HGDIOBJ被定義成空指針。
HPEN的實際編譯類型定義隨編譯時間宏STRICT的不同而不同。如果STRCIT已經
被定義了,HPEN是這樣的:
struct HPEN__ {int unused};
typedef struct HPEN__ HPEN;
如果STRICT沒有定義,HPEN是這樣定義的:
typedef void HANDLE;
typedef HANDLE HPEN;
上面這段代碼是一個注重細節的程序員最接近句柄的地方,因此我們重點分析
一下。這裡有一點點技巧。如果定義了STRICT宏,HPEN是指向有單個未使用欄位的
結構的指針,否則HPEN是空指針。C/C++編譯器允許把任何類型的指針作為空指什傳
遞,反之則不可以。兩個不同類型的非空指針是互不兼容的。在STRICT版本中,編
譯對GDI對象句柄的不正確混用將給出警告,對於非GDI句柄,如HWND、HMENU的不正
確混用也會給出警告,從而使程序在編譯器得到更STRICT的檢查。
接下來的分析可能不那麼令你感興趣,但它更深刻地揭示了句柄。對GDI句柄來
說,儘管windows頭文件把它定義成指針,但如果你仔細檢查這些句柄的值,它根本
就不像指針,這也是為什麼我說它只是一個32位無符整數值的原因。對句柄就是指
針的情況,這句話也仍然適用。讓我們隨意地生成一些句柄,比如你用GetStockOb
ject()以得到一些句柄,你會發現,它們的值總在區間0x01900011到0xba040389。
前者指向用戶區中的未分配的無效區域,後者指向內核地址空間。另外你可能發現
,兩個句柄之間的值可能只差數值1,這也說明GDI句柄不是指針。
和多數人想象的不一樣,句柄也不是一個單純的索引值。對GDI對象句柄來說,
GDI句柄由8位、1位堆對象標記(表明對象是否創建在堆中)、7位對象類型信息和
高4位為0的16位索引組成,如:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
10 9 8 7 6 5 4 3 2 1 0 98 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
| 8 位引用計數 |堆 | 對象類型7 | 16位索引 |
在這裡你可以看到,對GDI來說,它只使用了16位作為索引。這意味著一個進程最多只
可以創建小於64K個句柄,實際上受其他一些限制,整個Windows系統中大概可以容納約
16384(0x4000)個GDI對象。