源碼剖析——深入Windows控制代碼本質

來源:互聯網
上載者:User

標籤:

參考資料:

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

windef.h標頭檔

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

winnt.h標頭檔

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

  微軟官網中關於STRICT的內容

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

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

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標頭檔的第196、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.遺憾的是,到最後,還是有一些疑問沒有解決。這裡在下請求路過的大神不吝賜教,也誠望廣大讀者各抒己見,讓我們一起思考,共同進步。

 

 

源碼剖析——深入Windows控制代碼本質

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.