窗口句柄 - 計(jì)算機(jī)數(shù)據(jù)結(jié)構(gòu)
在Windows中,句柄是一個(gè)系統(tǒng)內(nèi)部數(shù)據(jù)結(jié)構(gòu)的引用,對象可以映射到唯一的句柄,句柄也可以映射到唯一的對象。如當(dāng)你操作一個(gè)窗口,或說是一個(gè)Delphi窗體時(shí),系統(tǒng)會(huì)給你一個(gè)該窗口的句柄,系統(tǒng)會(huì)通知你“你正在操作142號窗口”,就此你的應(yīng)用程序就能要求系統(tǒng)對142號窗口進(jìn)行操作——移動(dòng)窗口、改變窗口大小、把窗口最小化等等。實(shí)際上許多Windows API函數(shù)把句柄作為它的第一個(gè)參數(shù),如GDI(圖形設(shè)備接口)句柄、菜單句柄、實(shí)例句柄、位圖句柄等,不僅僅局限于窗口函數(shù)。換句話說,句柄是一種內(nèi)部代碼,通過它能引用受系統(tǒng)控制的特殊元素,如窗口、位圖、圖標(biāo)、內(nèi)存塊、光標(biāo)、字體、菜單等。
案例
獲取窗口句柄
案例說明
本例實(shí)現(xiàn)窗口句柄的獲取。
實(shí)現(xiàn)過程
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
概念
單從概念上講,句柄指一個(gè)對象的標(biāo)識,而指針是一個(gè)對象的內(nèi)存首地址。從實(shí)際處理的角度講,即可以把句柄定義為指針,又可以把它定義為同類對象數(shù)組的索引,這兩種處理方法都有優(yōu)缺點(diǎn),至于選用哪種方式,完全應(yīng)該看實(shí)際需要,這可以說是一種程序設(shè)計(jì)上的技巧。那種單純認(rèn)為句柄是指針或索引的想法都是機(jī)械的、不確切的。
其實(shí),在Windows中類似的處理是很多的、很靈活的。再舉個(gè)相似的例子:
我們知道,在Windows中有個(gè)函數(shù)叫做CallWindowProc。顧名思義,它的作用就是向指定的窗口過程傳遞一個(gè)消息。你也許會(huì)想,既然我已經(jīng)有了窗口過程的指針,為什么我不可以直接通過這個(gè)指針調(diào)用該函數(shù)(這是C語言的內(nèi)建功能)?事實(shí)上,在Win16中確實(shí)可以這么做,因?yàn)镚etWindowLong返回的確實(shí)是該函數(shù)的指針。但在Win32下,GetWindowLong返回的并不是該函數(shù)的指針,而是一個(gè)包含函數(shù)指針的數(shù)據(jù)結(jié)構(gòu)的指針(MSDN上說返回的是一個(gè)窗口函數(shù)地址或它的句柄,就是指的這種情況)。該數(shù)據(jù)結(jié)構(gòu)是可變的,但只要你使用CallWindowProc來調(diào)用的話是不會(huì)出錯(cuò)的。這里我們又看到使用句柄處理帶來的好處。(補(bǔ)充說明一點(diǎn):微軟在這里之所以這么處理,是為了解決16位/32位以及ANSI/UNICODE的轉(zhuǎn)化問題)
解疑
定義
句柄是什么?
在windows中,句柄是和對象一一對應(yīng)的32位無符號整數(shù)值。對象可以映射到唯
一的句柄,句柄也可以映射到唯一的對象。
用途
為什么我們需要句柄?
更準(zhǔn)確地說,是windows需要句柄。windows需要向程序員提供必要地編程接口
,在這些接口中,允許程序員訪問、創(chuàng)建和銷毀對象。但是,出于封裝地考慮,wi
ndows并不想向程序員返回指針。指針包含了太多的信息。首先指針給出了對象存儲(chǔ)
的確切位置;其次,要操作一個(gè)指針,程序員必須知道指針?biāo)笇ο蟮膬?nèi)部結(jié)構(gòu)特
征,也即,windows必須向程序員暴露相應(yīng)的數(shù)據(jù)結(jié)構(gòu),而這些數(shù)據(jù)結(jié)構(gòu)也許是操作
系統(tǒng)想向程序員隱藏的。
如果說COM技術(shù)向用戶隱藏了數(shù)據(jù),只暴露了接口并只允許按接口定義的方法操
作數(shù)據(jù)的話,句柄這種方式則允許你按自己的方式直接操作數(shù)據(jù),但windows又不向
你直接暴露數(shù)據(jù)。直接操作數(shù)據(jù)是程序員需要的,不暴露數(shù)據(jù)是windows所需要的,
句柄封裝方式實(shí)現(xiàn)了各取所需。
映射
句柄如何與對象映射
封裝背后,必須有一個(gè)地方可以實(shí)現(xiàn)解碼,以實(shí)現(xiàn)句柄和對象的相互轉(zhuǎn)換。在
windows中,存在兩種映射方式:
a. 全等映射。也即,句柄本身就是一個(gè)指針。映射在這里只是類型轉(zhuǎn)換而已。
這種情況有,進(jìn)程實(shí)例句柄或模塊句柄,以及資源句柄等等。
b. 基于表格的映射。這是對象指針與句柄之間最普通的映射機(jī)制。操作系統(tǒng)創(chuàng)
建表格,并保存所有要考慮的對象。需要?jiǎng)?chuàng)建新對象時(shí),要先在表格中找到空入口
,然后把表示對象的數(shù)據(jù)添入其中。當(dāng)對象被刪除時(shí),它的數(shù)據(jù)成員和其在表中的
入口被釋放。
實(shí)現(xiàn)
句柄的定義和實(shí)現(xiàn)
我們以GDI對象為例進(jìn)行討論。創(chuàng)建了GDI對象,就會(huì)得到該對象的句柄。句柄
的對象可能是HBRUSH、HPEN、HFONT或HDC中的一種,這依賴于你創(chuàng)建的GDI對象類
型。但是最普通的GDI對象類型是HGDIOBJ。HGDIOBJ被定義成空指針。
HPEN的實(shí)際編譯類型定義隨編譯時(shí)間宏STRICT的不同而不同。如果STRCIT已經(jīng)
被定義了,HPEN是這樣的:
struct HPEN__ {int unused};
typedef struct HPEN__ HPEN;
如果STRICT沒有定義,HPEN是這樣定義的:
typedef void HANDLE;
typedef HANDLE HPEN;
上面這段代碼是一個(gè)注重細(xì)節(jié)的程序員最接近句柄的地方,因此我們重點(diǎn)分析
一下。這里有一點(diǎn)點(diǎn)技巧。如果定義了STRICT宏,HPEN是指向有單個(gè)未使用字段的
結(jié)構(gòu)的指針,否則HPEN是空指針。C/C++編譯器允許把任何類型的指針作為空指什傳
遞,反之則不可以。兩個(gè)不同類型的非空指針是互不兼容的。在STRICT版本中,編
譯對GDI對象句柄的不正確混用將給出警告,對于非GDI句柄,如HWND、HMENU的不正
確混用也會(huì)給出警告,從而使程序在編譯器得到更STRICT的檢查。
接下來的分析可能不那么令你感興趣,但它更深刻地揭示了句柄。對GDI句柄來
說,盡管windows頭文件把它定義成指針,但如果你仔細(xì)檢查這些句柄的值,它根本
就不像指針,這也是為什么我說它只是一個(gè)32位無符整數(shù)值的原因。對句柄就是指
針的情況,這句話也仍然適用。讓我們隨意地生成一些句柄,比如你用GetStockOb
ject - 以得到一些句柄,你會(huì)發(fā)現(xiàn),它們的值總在區(qū)間0x01900011到0xba040389。
前者指向用戶區(qū)中的未分配的無效區(qū)域,后者指向內(nèi)核地址空間。另外你可能發(fā)現(xiàn)
,兩個(gè)句柄之間的值可能只差數(shù)值1,這也說明GDI句柄不是指針。
和多數(shù)人想象的不一樣,句柄也不是一個(gè)單純的索引值。對GDI對象句柄來說,
GDI句柄由8位、1位堆對象標(biāo)記(表明對象是否創(chuàng)建在堆中)、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 位引用計(jì)數(shù) |堆 | 對象類型7 | 16位索引 |
標(biāo)
記
在這里你可以看到,對GDI來說,它只使用了16位作為索引。這意味著一個(gè)進(jìn)程最多只
可以創(chuàng)建小于64K個(gè)句柄,實(shí)際上受其他一些限制,整個(gè)Windows系統(tǒng)中大概可以容納約
16384 - 0x4000個(gè)GDI對象。
