句柄與指針的區別

原帖地址(http://mahanyang94.blog.163.com/blog/static/25498051200911176212455/

句柄其實就是指針,但是他和指針最大的不同是:給你一個指針,你可以通過這個指針做任何事情,也許是好事,也許是通過這個指針破壞內存,幹一些搗亂的事情。這個我想大家都會碰到過,因爲亂用指針導致程序崩潰
    句柄就沒有這個缺點,通過句柄,你只能幹一些windows讓你乾的事情(調用一些api函數等等),沒有了指針的壞處。

    句柄是一些表的索引也就是指向指針的指針,句柄和指針都是地址,句柄是Windows編程的一個關鍵性的概念,編寫Windows應用程序總是要和各種句柄打交道。
    所謂句柄,就是一個唯一的數,用以標識許多不同的對象類型,如窗口、菜單、內存、畫筆、畫刷等。在Win32裏,句柄是指向一個“無類型對象”(void*)的指針,也就是一個4字節長的數據。
    無論它的本質是什麼,句柄並不是一個真正意義上的指針。從構造上看,句柄是一個指針,儘管它沒有指向用於存儲某個對象的內存位置。事實上,句柄指向一個包含了對該對象進行的引用的位置。
    句柄的聲明是這樣的:
    typedef void *HANDLE
    由於Windows是一個多任務操作系統,它可以同時運行多個程序或一個程序的多個副本。這些運行的程序稱爲一個實例。爲了對同一程序的多個副本進行管理,Windows引入了實例句柄。Windows爲每個應用程序建立一張表,實例句柄就好象是這張表的一個索引。
    不同在於:
      1、句柄所指的可以是一個很複雜的結構,並且很有可以是與系統有關的,比如說上面所說的線程的句柄,它指向的就是一個類或者結構,他和系統有很密切的關係,當一個線程由於不可預料的原因,而終止時在系統就可以回它所佔用的資料,如CPU,內存等等,反過來想可以知道,這個句柄中的某一些項,是與系統進行交互的。由於Windows系統,是一個多任務的系統,它隨時都可能要分配內存,回收內存,重組內存。
      2、指針它也可以指向一個複雜的結構,但是通常是用戶定義的,所以的必需的工作都要用戶完成,特別是在刪除的時候。但在VC++6.0中也有一些指針,它們都是處理一些小問題才用的,如最常見的字符的指針,它也是要用戶處理的如果你動態分配了內存;但是Cstring 就不要用戶處理了,它其實是VC++中的一個類,所以的操作都由成員函數完成,產生(分配)由構造函數,刪除(回收)由析構函數完成。


獲得窗口句柄三種方法

.HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)

HWND FindWindowEx(HWND hwndParent, HWND hwndChildAfter,LPCTSTR lpClassName, LPCTSTR lpWindowName)

2.HWND WindowFromPoint(POINT& Point)//獲得當前鼠標光標位置的窗口HWND

3.BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)

BOOL CALLBACK EnumChildWindows(HWND hWndParent, WNDENUMPROC lpEnumFunc,LPARAM lParam)
BOOL CALLBACK EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

指針 句柄之間的轉換

a.由指針獲得句柄
CWnd * pWnd;
CWnd HWnd;
HWnd = pWnd->GetSafeHWnd();

b.由句柄得到指針:
CWnd* pWnd=FromeHandle(hMyHandle);
pWnd->SetWindowText("Hello World!");
or CWnd* pWnd; pWnd->Attach(hMyHandle);

MFC類中有的還提供了標準方法,比如Window 句柄:
static CWnd* PASCAL FromHandle( HWND hWnd );
HWND GetSafeHwnd( ) const;

對於位圖:
static CBitmap* PASCAL FromHandle( HBITMAP hBitmap );
static CGdiObject* PASCAL FromHandle( HGDIOBJ hObject );
HGDIOBJ GetSafeHandle( ) const;

    有人說句並就是一個標示,一個ID號,是錯誤的。一個ID號可以包括多個資源,比如說單文檔中的IDR_MAINFRAME,一般是指在硬盤上的資源。但是當把硬盤上的資源調入內存以後,將有一個句柄指向它,但是句柄只能指向一個資源。而且句柄知道所指的內存有多大。還有指針,指針指向地址,它不知道分配的內存有多大。
    但是如果你定義一個句柄,然後在VC裏面右擊鼠標,選擇"go to definition of HANDLE,你會發現它的本質就是一個指針,但是它的作用不同於指針。

    句柄是個指針,指向一塊內存,但至於這塊內存跟句柄所標識的對象是怎麼聯繫起來的,調用者不需要清楚,調用者只需要知道,這個句柄聯繫着一個win32對象。
    句柄是物理地址,可以跨進程傳遞,例如,HANDLE ha進程A的一個窗口,你可以在進程B中利用一個跟ha相等的值(相等就是說它們強制轉成int32的值相等)初始化一個句柄,利用這個句柄你可以對進程 A的那個對象進行操作,例如MoveWindow,ShowWindow等。
    句柄包含了一些引用計數之類的東西,所以我的上一點說的給句柄賦值是不安全的,Windows API提供了一些函數,可以對句柄進行操作。

句柄就是受限的指針。
    它是由操作系統管理的,你不能通過它存取操作系統創建的數據結構。

    操作系統在創建一個對象(如GDI, FILE)等的時候,它會爲這個對象CONTEXT保留一塊數據結構,然後把它放在一張全局表中。。句柄就是這塊數據結構在表中的索引

    指針對應着一個數據在內存中的地址,得到了指針就可以自由地修改該數據。Windows並不希望一般程序修改其內部數據結構,因爲這樣太不安全。所以 Windows給每個使用GlobalAlloc等函數聲明的內存區域指定一個句柄(本質上仍是一個指針,但不要直接操作它),平時你只是在調用API函數時利用這個句柄來說明要操作哪段內存。當你需要對某個內存進行直接操作時,可以使用GlobalLock鎖住這段內存並獲得指針來直接進行操作。
    lshgao的意見:
    句柄是指針的“指針”,使用句柄主要是爲了利於windows在進程內存地址空間移動分配的內存塊,以防止進程的內存空間被撕的四分五裂而存在過多的碎片。
   
    阿城的意見:
    句柄是一些表的索引也就是指向指針的指針。間接的引用對象,windows可以修改對象的"物理"地址和
    描述器的值,但是句柄的值是不變的。
   
    劉志用的意見:
    句柄和指針都是地址,不同在於:
    1,句柄所指的可以是一個很複雜的結構,並且很有可以是與系統有關的,比如說上面所說的線程的句柄,它指向的就是一個很類或者結構,他和系統有很密切的關係,當一個線程由於不可預料的原因,而終止時在系統就可以回它所佔用的資料,如CPU,內存等等,反過來想可以知道,這個句柄中的某一些項,是與系統進行交互的。由於Windows系統,是一個多任務的系統,它隨時都可能要分配內存,回收內存,重組內存。
    2,指針它也可以指向一個複雜的結構,但是通常是用戶定義的,所以的必需的工作都要用戶完成,特別是在刪除的時候。
    但在VC++6.0中也有一些指針,它們都是處理一些小問題才用的,如最常見的字符的指針,它也是要用戶處理的如果你動態分配了內存;但是Cstring 就不要用戶處理了,它其實是VC++中的一個類,所以的操作都由成員函數完成,產生(分配)由構造函數,刪除(回收)由析構函數完成。
   
    zjf問:
    你好,我在學習用vc++6.0編譯多線程程序中遇到了很多句柄,但是不明白他的具體作用以及如何使用句柄,希望您能給我舉幾個具體實例,不甚感激!
    比如說: HANDLE hThread,它是怎樣具體使用的?
    答:你使用CreateThead後函數會返回一個句柄,它代表這個線程。你可能會調用SetThreadPriority去修改線程的優先級,使用 ResumeThread去重新開始一個線程的運行,在調用這些函數時你都需要告訴系統你到底要操作哪個線程,而剛纔返回的句柄派上用處了,這些函數的第一個參數就是線程的句柄。
    看VC中總是出現這個句柄的概念,以前一直以爲就是指指針,但是越看越覺得不是這麼簡單,於是本着有問題百度一下的原則,看到如下解釋,很是經典:

    csdn上有人說過:牧童遙指杏花村。
    牧童的手爲指針,杏花村的牌子爲句柄,杏花村酒店爲對象的實例.

    句柄就是烤叉,用烤爐烤過鴨,雞,牛,羊,狗麼?
    爐子裏的東西是看不見,摸不到的,但你能用叉子去控制,至於叉子上的是什麼,你放進去前應該記住。呵呵

    句柄有時是指針,有時是索引,但他絕對是一把鑰匙,內核句柄110的鑰匙,GDI句柄是您的鑰匙,只對您有效。

    單從概念上講,句柄指一個對象的標識,而指針是一個對象的首地址。從實際處理的角度講,即可以把句柄定義爲指針,又可以把它定義爲同類對象數組的索引,這兩種處理方法都有優缺點,至於選用哪種方式,完全應該看實際需要,這可以說是一種程序設計上的技巧。那種單純認爲句柄是指針或索引的想法都是機械的、不確切的。其實,在Windows中類似的處理是很多的、很靈活的。再具個相似的例子:

    我們知道,在Windows中有個函數叫做CallWindowProc。
    故名思義,它的作用就是向指定的窗口過程傳遞一個消息。你也許會想,既然我已經有了窗口過程的指針,爲什麼我不可以直接通過這個指針調用該函數(這是C語言的內建功能)?事實上,在Win16中確實可以這麼做,因爲GetWindowLong返回的確實是該函數的指針。但在Win32 下,GetWindowLong返回的並不是該函數的指針,而是一個包含函數指針的數據結構的指針(MSDN上說返回的是一個窗口函數地址或它的句柄,就是指的這種情況)。該數據結構是可變的,但只要你使用CallWindowProc來調用的話是不會出錯的。這裏我們又看到使用句柄處理帶來的好處。(補充說明一點:微軟在這裏之所以這麼處理,是爲了解決16位/32位以及ANSI/UNICODE的轉化問題)

    看來,句柄很多時候是一個用於描述和標記一個資源的數據結構的指針,而不是資源本身的指針,句柄中可能包含資源的指針,但是根多時候不僅僅是這樣。

句柄vs指針
    句柄是一種指向指針的指針。我們知道,所謂指針是一種內存地址。應用程序啓動後,組成這個程序的各對象是住留在內存的。如果簡單地理解,似乎我們只要獲知這個內存的首地址,那麼就可以隨時用這個地址訪問對象。但是,如果您真的這樣認爲,那麼您就大錯特錯了。我們知道,Windows是一個以虛擬內存爲基礎的操作系統。在這種系統環境下,Windows內存管理器經常在內存中來回移動對象,依此來滿足各種應用程序的內存需要。對象被移動意味着它的地址變化了。如果地址總是如此變化,我們該到哪裏去找該對象呢?爲了解決這個問題,Windows操作系統爲各應用程序騰出一些內存儲地址,用來專門登記各應用對象在內存中的地址變化,而這個地址(存儲單元的位置)本身是不變的。Windows內存管理器在移動對象在內存中的位置後,把對象新的地址告知這個句柄地址來保存。這樣我們只需記住這個句柄地址就可以間接地知道對象具體在內存中的哪個位置。這個地址是在對象裝載(Load)時由系統分配給的,當系統卸載時 (Unload)又釋放給系統。句柄地址(穩定)→記載着對象在內存中的地址→對象在內存中的地址(不穩定)→實際對象。但是,必須注意的是程序每次從新啓動,系統不能保證分配給這個程序的句柄還是原來的那個句柄,而且絕大多數情況的確不一樣的。假如我們把進入電影院看電影看成是一個應用程序的啓動運行,那麼系統給應用程序分配的句柄總是不一樣,這和每次電影院售給我們的門票總是不同的一個座位是一樣的道理。

 

指針與句柄的區別

    對於Wind32 API,儘管爲每個對象分配了數據塊,但是微軟不想向用戶程序返回指針。對於一個聰明的程序員來說,指針包含了太多的信息。它給出了對象存儲的確切位置。指針一般允許對對象的內部表示進行讀寫操作,而這些內部表示也許正是操作系統想隱瞞的。指針還使越過進程地址空間共享對象變得困難。爲了對程序員進一步隱藏信息,Win32對象創建程序實例一般會返回對象句柄。對象可以映射到唯一句柄,句柄也可由映射到唯一的對象。爲了保證句柄能夠完成信息隱藏的的任務,對象和句柄之間的映射沒有任何文檔記載,不保證固定不變,而已僅有微軟知道這種映射,或者還有少數系統級工具開放商知道。
    對象指針和句柄之間的映射可以由函數Encode和Decode來實現,原型如下:
    HANDLE Encode(void* pObject);
    Void* Decode(HANDLE hObject);
    在極端情況下,句柄可以和對象指針相同,Encode和Decode只需做類型轉換,對象和句柄之間的映射主要是全等映射。
    在Win32 API中,實例句柄(HINSTANCE)或者模塊句柄(HMODULE)是指向映射到內存的PE文件映像的指針。LockResource用來鎖住全局資源句柄得到指針,但實際上它們的值相同。LockResource返回的資源句柄只是僞裝後的內存映射資源的指針。
    通常情況下,對象指針和句柄之間的映射是基於表格的映射。操作系統創建表格或者是一級表示保存所有要考慮的對象。需要創建新對象時,首先要在表格中找到空入口。然後就把表示對象的數據添入其中。當對象被刪除時,它的數據成員和它在中的入口被釋放,以便再利用入口。用這種基於表的對象管理方法,表中的索引可以很好的組成對象的句柄,編碼和解碼也很簡單。
    (在Win32 API中,內核對象是用進程表實現的。爲了容納大量內核對象,每個進程都有自己的內核對象表。NT/2000內核執行體中一部分是對象管理器,它只管理內核對象。對象管理器提供函數ObReferenceObjectByHandle。根據DDK(Driver Develepment Kits)文檔,它提供對象指針的解碼全過程,如果存取操作被批准,則會返回對象體相應的指針。因此對於一個把對象句柄翻譯稱爲對象指針的解碼全程來說,額外的安全檢查很重要。
www.internals.com上面有個非常好的工具HandleEx,它能夠列出Windows NT/2000的內核對象。
    只有句柄是不夠的,儘管句柄提供了近乎完美的抽象,信息隱藏和保護,但是它也是程序員遭受挫折的地方。在像Win32 API這樣以句柄爲中心的API中,微軟沒有任何文檔記載對象的內部表示以及對象是如何管理的,也沒有提供參考實現,程序員只有函數原型,微軟文檔和或多或少基於微軟文檔的書籍。程序員面臨的首要問題包括系統資源。當對象被創建,對象的句柄被返回時,誰都不知道對象用了什麼資源,因爲對象的內部表示是不知道的。程序員是應該保護該對象還是應該在對象沒有用時儘快把它刪除呢?GDI支持的三種位圖,爲了減少系統資源消耗,應該使用哪一種呢?CPU時間時計算機的主要資源。當內部表示對程序員隱藏時,程序員就很難在複雜的算法設計中判斷這種操作的複雜性如果你用GDI組成複雜區域,算法的複雜度是O(n)(問題規模n),O( )(問題規模)還是O()。隨着程序的隱藏,調試也成問題。程序運行5分鐘顯示了一些垃圾數據,猜測由資源泄漏,但是泄漏在哪兒?怎麼解決?如果是處理系統中幾百個應用程序的管理員,當系統資源很少時,如果找出問題?唯一可以用的資源泄漏工具是BoundsChecker,它依賴API窺視技術查出對象創建和刪除之間的不匹配之處。最讓人受挫的地方可能是程序的兼容性。程序爲什麼能在Windows95下把GDI對象從一個進程傳遞到另外一個進程,而 Windows NT/2000不行?爲什麼Windows95不能處理大的設備無關圖?
    以GDI對象爲例子,創建了GDI對象,就會得到該對象的句柄。句柄的類型有可能是HPEN,HBRUSH,HFONT或者是HDC中的一種。但最普通的 GDI對象類型是HGDIOBJ,它被定義成爲空指針。HPEN的實際編譯類型是隨着時間宏STRICT的不同而不同。不同GDI句柄的定義模仿了GDI 對象不同類的類層次結構,但是沒有真正的類層次結構。GDI對象一般有多個創建函數和一個接受HGDIOBJ的析構函數——DeleteObject。也可以用GetStockObject取得預先創建好的GDI對象句柄,無論GetStockObject調用順序是如何,它返回的句柄看起來總是常數。甚至當運行一個程序的兩個實例時,它在每個進程中返回相同的,唯一解釋是對象句柄堆是不變的,系統初始化,堆對象被創建並被所有進程重複使用。儘管 Windows頭文件把GDI句柄定義成爲指針,但是檢查這些句柄的值時,它們根本不像指針。生成幾個GDI對象句柄並看一下返回句柄的十六進制顯示,就會發現結果從0x01900011變化到0xba040389。如果HGDIOBJ像在Windows頭文件裏面定義的那樣是指針,則前者是指向用戶地址空間中未分配的無效指針,而後者是執行內核地址空間。這就暗示GDI句柄不是指針。另一個發現是GetStockObject(BLACK_PEN)和 GetStockObject(NULL_PEN)返回值只相差一,如果句柄真的是指針的話,這不可能是存儲內部GDI對象的空間,因此可以肯定的說 GDI對象句柄不是指針。系統GDI句柄數限制爲16384個,進程GDI句柄數限制爲12000個。這樣單獨的進程不會搞亂整個GDI系統。但是 Windows 2000第一版沒有對每個進程加以限制。現在在同一個系統下運行兩個GDIHandles,在每一個進程中調用8192次CreatePen。第一個很好的創建了對象,第二個在7200左右會停止。第二個進程失敗後,整個屏幕一團糟,這個試驗表示GDI對象同樣是從同一個資源池分配的。系統中的進程使用 GDI資源時會互相影響。把8192和7200相加。考慮到GDIHandle屬性頁面和其它進程的頁面使用的GDI對象句柄,可以猜測,GDI句柄數目有系統範圍限制:16384。GDI對象存儲於系統範圍內的固定大小的對象表中,稱之爲對象句柄表。它是一個大小固定的表,而不是一個會動態增長的數據結構。這就意味着簡明和效率。但是     缺點就是前面說的,限制了GDI句柄數:16384個。下面看看HGDIOBJ的構成,Windows NT/2000下,GDI返回的對象句柄是32位值,組成8位十六進制數。如果用GDIHandles創建很多GDI對象,注意到其中顯示的雙字句柄的低位字,會發現它們都在0x000到0x3FFF之間。低位字在進程中總是唯一的,出了堆對象外,低位字甚至在進程中也是唯一的。句柄的低位有時候按照遞增的順序排列,有時候又遞減。在進程間也是這樣。例如,某些情況下,CreatePen在低位返回0x03C1,另一個進程中的下一個CreatePen在低位返回0x03C3。對這些現象的解釋是HGDIOBJ的低位字是對系統範圍的16384個GDI對象所組成的表的索引。再來關注高4位的十六進制數。創建幾個畫刷,幾個畫筆,幾個字體,DC等。不難看出相同類型的GDI對象句柄有個共同特點:相同類型的對象句柄的第三位和第四位十六進制數幾乎都是相同的。畫刷句柄的第三位和第四位總是0x90和0x10,畫筆總是0x30和0xb0等等。最高位是1(二進制)的對象句柄都是堆對象。因此可以有足夠的證據說對象句柄的第三位和第四位十六進制數是對象類型的編碼和堆對象標記。在32位GDI句柄值中餘下的兩個十六進制位暫時還沒找到有意義的模式。總結一下,GDI對象句柄由8位位置高位,一位堆對象標記,7位對象類型信息和高四位爲0的16位索引組成。因爲GDI對象表是由系統中所有過程和系統DLL所共享的,桌面,瀏覽器,字處理器以及DirectX遊戲都在爲同一個GDI句柄的儲存區而競爭。而在Windows 2000中,DirectX則使用一個獨立的對象句柄表。GDI句柄表一般存儲在內核模式的地址空間裏以使圖形引擎能很容易訪問它,通過一定技巧,將爲每個使用GDI的進程在用戶模式存儲空間裏面建立表的只讀視圖。在Windows 2000終端服務中,每個對話都有它自己的Windows圖形引擎和視窗管理器(WIN32K.SYS)的拷貝,以至於系統中有多個GDI對象表。
    GDI句柄表的每一個入口都是一個16字節的結構體,如下面代碼所示: Typedef struct 

    {  

    void*        pKernel;  

    unsigned short nPaid;  

    unsigned short nCount;  

    unsigned short nUnique;  

    unsigned short nType;  

    void*        pUser;  

} GdiTableEntry; 
複製代碼可見:GDI對象有一個指向它的內核模式對象的指針,一個指向其用戶模式的指針,一個過程ID,一個種類的計數,一個唯一性的標準值以及一個類型標識符。多麼完美,優雅的設計!
    儘管Win32 API不是面相對象的API,但它也面臨着和麪相對象語言一樣要解決的問題,即信息的隱藏和抽象數據類型,而且Win32 API比面相對象語言更想隱藏對象。用於創建Win32對象的Win32函數隱藏了該對象的大小和儲存位置,而且沒有返回指向對象的指針,而是僅僅返回該對象的句柄。Win32 API句柄是Win32對象一一對應的變量,它僅能被操作系統識別。分析一下,你會發現Win32使用了相當多的抽象數據類型,如文件對象,它包括了許多具體的對象類型,我們可以用CreateFile來創建文件,管道,通訊端口,控制檯,目錄以及設備等,但是這些操作都返回一種類型的句柄。跟蹤到 WriterFile中,會發現最後的操作其實是操作系統中不同例程甚至是不同產商提供的設備驅動程序來處理的,這就像是C++的虛函數和多態機制。同樣,在GDI域中,設備上下文被當作一個抽象對象類型看待。創建或者檢索打印機設備上下文,顯示設備上下文,內存設備上下文和圖元文件上下文的程序都將返回同類型的設備上下文句柄。顯而易見的是,同年國國句柄的類屬繪圖調用是通過不同的例程來處理的,而這些例程但是由GDI或者圖形設備驅動程序通過物理設備結構中的函數指針表來實現的。因此實現這樣的機制也會像C++一樣有一個類似於虛函數表的函數指針表,一個變量和函數指針通過這個表形成映射,方便的實現這種虛函數和多態機制,這個變量就是句柄....

發佈了1 篇原創文章 · 獲贊 9 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章