源碼剖析——深刻Windows句柄本質

參考資料:html

1. http://www.codeforge.cn/read/146318/WinDef.h__html程序員

windef.h頭文件編程

2. http://www.codeforge.cn/read/146318/WinNT.h__htmlwindows

winnt.h頭文件編程語言

3. https://msdn.microsoft.com/en-us/library/windows/desktop/aa383681%28v=vs.85%29.aspx函數

  微軟官網中關於STRICT的內容url

4.http://wenku.baidu.com/link?url=j0ubLizIjhgmxthACfwBa4IpXrdqyyFg84a9MPmwusN4XhalR94kVaDAeR6GlFCMVD_AQORTLyfEC84-tqUWo27dziBXKjNdAqXe8Ich0euspa

  C語言宏中"#"和"##"的用法.net

5. http://www.cnblogs.com/kerwinshaw/archive/2009/02/02/1382428.html指針

  typedef和#define的用法與區別

6. http://blog.csdn.net/geekcome/article/details/6249151

  void及void指針含義的深入解析

寫在前面:

      本文是對在下上一篇文章《圖解說明——究竟什麼是Windows句柄》的擴充。一樣地,本文依然是面向初學者的。讓編程語言變得平易,讓初學者學起來你更加舒服,在交流中與廣大讀者同勉共進,是在下的一向宗旨和追求。因此,在對源碼的解釋中,在下會盡量作到詳細,具體到句,對於與句柄不是特別相關的內容,也會加以解釋說明。和上一篇同樣,咱們仍然把窗口、位圖、畫筆等統稱爲對象。

還有一點必需要交代,在下對Windows句柄這一細節的研究,還存在一些疑問,這將在文末進一步說明。

先看winnt.h中關於HANDLE(句柄)的定義:

typedef void *PVOID;

 

#ifdef STRICT

typedef void *HANDLE;

#define DECLARE_HANDLE(name) struct name##__ {

 int unused;

};

typedef struct name##__ *name

#else

typedef PVOID HANDLE;

#define DECLARE_HANDLE(name) typedef HANDLE name

#endif

      以上代碼typedef void *PVOID;來自winnt.h(參考資料2)中的第178行,其他代碼來自winnt.h中的第285~293行,考慮到易讀性,在下對代碼格式稍稍作了調整。

     在分析源代碼以前,再說一點,那就是typedef和#define的區別的問題。typedef用來定義一個標識符及關鍵字的別名,而#define是宏定義,簡單說,就是字符串替換。若是有讀者還不是很明白,能夠參閱參考資料5。在下面的敘述中,咱們將二者都譯爲「定義」。由於在下以爲這樣能夠帶來敘述上的方便,而且若是你們理解了typedef和#define的區別,這樣作並不會形成理解上的誤會。

下面咱們開始逐句分析代碼。

首先,typedef void *PVOID;,這裏將PVOID定義爲void*型,之後,PVOID a,b;就至關於void *a,*b;(注意不是void *a,b;)。這裏再簡單說一下void*,簡單說,void *就是「無類型指針」,能夠指向任何數據類型,詳情可參閱參考資料6。

下面的一段整體上是if——else結構。咱們先看if部分。

#ifdef STRICT

若是定義了STRICT,就執行後面的代碼。關於STRICT,在後面咱們還會進行詳細的講解,這裏咱們暫時將其跳過,先看條件成立時的代碼。

#define DECLARE_HANDLE(name) struct name##__ {

 int unused;

};

這裏是一個帶參數的宏定義,name是參數,##爲粘貼符號,表示把左右兩邊的內容鏈接起來。關於帶參數的宏定義和##,讀者能夠參閱參考資料4。

這裏將結構體

struct name##__

{

 int unused;

};

定義爲

DECLARE_HANDLE(name)。

接下來,

typedef struct name##__ *name

定義一個指針name,指向上面的結構體name##__。

下面咱們以窗口句柄HWND爲例,進一步說明。

在windef.h頭文件(見參考資料1)的第196行有代碼

DECLARE_HANDLE            (HWND);

咱們將宏展開,就是

struct HWND__

{

     int unused;

};

一樣,根據typedef struct name##__ *name

有typedef struct HWND__ *HWND。

即句柄HWND是一個指針,指向結構體struct HWND__。

其它句柄的定義與HWND相似,這裏再也不贅述,讀者能夠參閱參考資料1中從195行日後的代碼。

注意這裏咱們忽略了一個細節,那就是結構體中的int unused。關於這一點,咱們先暫時忽略,在後面的「還沒有解決」板塊,在下將對這一問題做出交代。

有了前邊的經驗,分析else部分的代碼就變得容易了,讓咱們一塊兒來看。

typedef PVOID HANDLE;

因爲前邊有typedef void *PVOID;,因此這裏HANDLE被定義爲void*型。

接着,

#define DECLARE_HANDLE(name) typedef HANDLE name

這裏將

typedef HANDLE name

定義爲

DECLARE_HANDLE(name)。

還以HWND爲例,在這種狀況下,

DECLARE_HANDLE            (HWND);

宏展開爲

typedef HANDLE HWND,

即此時HWND爲void*型。

好了,說完這些,咱們着重說一下STRICT。相關內容請參閱參考資料3。

在windef.h頭文件的第13~17行定義了STRICT,源代碼以下:

#ifndef NO_STRICT

#ifndef STRICT

#define STRICT 1

#endif

#endif /* NO_STRICT */

       這裏僅僅是將STRICT定義爲數值1,看不出什麼名堂。關鍵在於編譯器(注意不是系統)對STRICT的「解釋」。

       顧名思義,STRICT是「嚴格」、「嚴厲」的意思。當編譯器「看到」定義了STRICT後,就會對Windows 應用程序中使用的句柄進行嚴格的類型檢查。Windows官網中的原文爲Enabling STRICT redefines certain data types so that the compiler does not permit assignment from one type to another without an explicit cast.

也就是說,若是定義了STRICT,除非顯式強制類型轉換,不然不容許將數據從一種類型轉化到另外一種類型。換句話說,定義STRICT能夠禁止隱式類型轉換。

       那麼,這是怎麼實現的,又有什麼用處呢? 以窗口句柄HWND和鉤子句柄HHOOk爲例。在windef.h頭文件的第19六、197行定義了HWND和HHOOK:

DECLARE_HANDLE            (HWND);

DECLARE_HANDLE            (HHOOK);

      經過前面的分析,咱們知道,若是定義了STRICT,那麼天然執行#ifdef STRICT後的代碼,這樣,HWND就是HWND__*型的指針,而HHOOK就是HHOOK__*型的指針,二者類型不一樣。若是沒有定義STRICT,那麼將執行#else後的代碼,能夠發現,這段代碼直接將全部句柄都定義爲HANDLE,即PVOID,也就是void*型。在這種狀況下,上面的HWND和HHOOK都是void*型,類型相同。那麼,兩種狀況下有什麼差異呢?咱們舉例說明。

      如今一個函數要求一個HHOOK類型的參數,而咱們傳給它一個HWND類型的參數。在沒有define STRICT的狀況下,這將是合法的,由於HHOOK和HWND都是void*類型。而若是咱們定義了STRICT,HHOOK和HWND就是兩個不一樣類型的指針,上面的參數傳遞將變爲不合法,而且在編譯階段就會報錯,這就避免了直到程序出現了運行時錯誤,程序員才知道代碼有錯的狀況。

      順便說一句,如今VC、VS都define了STRICT,即都默認進行嚴格的類型檢查。

      至此,相信你們已經明白了STRICT的做用以及爲何不直接用int unused而要用結構體將其封裝起來。

      最後,讓咱們一言以蔽之,來總結一下Windows句柄的本質:

      Windows句柄本質上就是一個指向結構體的指針(define STRICT的狀況下)

      而所謂「指針的指針」的說法並不正確,這只是一個邏輯上的理解。

還沒有解決:

      如今,讓咱們回到前面忽略掉的關於int unused的問題上來。

      若是有讀者看過了在下的上一篇文章《圖解說明——究竟什麼是Windows句柄》,那麼相信有人會和在下最初的想法同樣,認爲這裏的unused就是咱們說的區域A。句柄指向一個結構體,而這個結構體中惟一的數據unused中存放着對象的地址(雖然unused不是指針類型,但int和指針同爲4個字節,將對象的地址存到unused裏,未來再用某種方式經過unused找到該對象,這也是能夠實現的),這與咱們先前的圖示剛好吻合。但稍加琢磨,咱們發現這樣解釋在某些地方仍是有些說不過去的。理由起碼有2:

①首先是名字問題,相信稍微細心的讀者就會發現這一問題。經過前邊的源代碼看,每個名字都恰如其分地反映了它應有的意義,照這麼看,結構體中的int變量存放了一個有用的地址,那它就不該該叫unused。

②若是unused至關於區域A的話,在define STRICT的狀況下,句柄指向了區域A,而在沒有define STRICT的狀況下,並無定義結構體,句柄被定義爲void*型,那麼,這種狀況下的區域A又在哪裏,句柄又如何指向它?

      因此,綜合前面的分析,在下認爲,unused並非區域A。關於int unused,在下的一個猜測是:

      進程建立時,系統在內存的一個地方存放各個對象的地址,同時系統爲各個對象指定句柄,存放在內存中另外一個地方,並使各個句柄指向相應對象的地址(即區域A)。至於如何指向,極可能是這樣:

      對於未define STRICT的狀況,直接指向就能夠,由於此時句柄是void*型。而對於define了STRICT的狀況,可能採用強制類型轉換或是類似的手段來使原先指向結構體的句柄指向一個32位的地址。注意到,原先句柄指向一個結構體,而這個結構體中只有一個int型數據,從內存的角度看,句柄其實指向了一段4字節的內存,然後來,句柄指向32位的地址,一樣是指向一段4字節的內存。而若是咱們去掉intunused,只保留一個空結構體,咱們知道,空結構體佔1個字節(對這一點有疑問的讀者,能夠寫一個空結構體,用sizeof()函數實測一下),此時句柄指向1個字節的內存,而如今要讓它指向4個字節的內存(32位地址),極可能會沒法「轉化」。然而,若是轉化先後,句柄都指向4個字節的內存,那極可能就可以轉化。因此,int unused的做用就是使句柄指向一個4字節的內存,以便未來句柄指向對象的地址時可以順利「轉化」。而從始至終,unused歷來沒有被顯式地使用過,因此取名爲unused。顯然,這裏unused的意思是「未被使用的」,而非「沒用的」。

     至此,全部關於windows句柄這一細節的內容都講解完了。

寫在後面:

1.在下知識淺薄、能力有限,講解過程當中不免有錯誤疏漏之處,這裏懇請你們務必批評指正,在下先行謝過。

2.遺憾的是,到最後,仍是有一些疑問沒有解決。這裏在下請求路過的大神不吝賜教,也誠望廣大讀者各抒己見,讓咱們一塊兒思考,共同進步。

相關文章
相關標籤/搜索