• 
    

    
    

      99热精品在线国产_美女午夜性视频免费_国产精品国产高清国产av_av欧美777_自拍偷自拍亚洲精品老妇_亚洲熟女精品中文字幕_www日本黄色视频网_国产精品野战在线观看 ?

      Thunk技術(shù)原理及其在Windows系統(tǒng)窗口類封裝中的應(yīng)用

      2018-06-02 08:50:48尹德有楊振龍
      電腦知識與技術(shù) 2018年10期

      尹德有 楊振龍

      摘要:該文主要介紹了Thunk技術(shù)的基本原理,以及如何利用Thunk技術(shù)封裝基于C++語言的Windows窗口類。該文還介紹了如何利用ATL中已有的Thunk代碼實現(xiàn)窗口類的封裝以簡化代碼的編寫工作。利用本文介紹的技術(shù)封裝窗口類可極大的精簡代碼規(guī)模,同時代碼的運行效率也較高。

      關(guān)鍵詞:Thunk;C++;Windows;窗口類;ATL;形實轉(zhuǎn)換

      中圖分類號:TP311 文獻標(biāo)識碼:A 文章編號:1009-3044(2018)10-0248-03

      一直以來,使用C++語言封裝Windows窗口類都是一個比較繁瑣的工作。大多數(shù)軟件開發(fā)人員都是使用第三方類庫進行Windows窗口類的編寫,比如最常使用的就是Microsoft公司的MFC類庫以及ATL模板庫等。這些類庫雖然功能強大,但是它們都過于龐大、復(fù)雜,如何使用簡單的技術(shù)實現(xiàn)輕量級的Windows窗口類(以下簡稱窗口類)是廣大軟件開發(fā)人員一直在探討的問題。

      封裝窗口類的難點是如何讓W(xué)indows窗口處理回調(diào)機制在消息發(fā)生時調(diào)用窗口類的非靜態(tài)成員函數(shù)。

      我們知道,C++在調(diào)用非靜態(tài)成員函數(shù)時需要傳遞this指針,由于Windows系統(tǒng)只能調(diào)用靜態(tài)的回調(diào)函數(shù),而類的靜態(tài)成員函數(shù)是沒有this指針的,因此,如何將this指針傳遞給類的靜態(tài)成員函數(shù)(窗口處理回調(diào)函數(shù))就成為封裝窗口類的關(guān)鍵技術(shù)。到目前為止,向類的靜態(tài)成員函數(shù)傳遞this指針的方法主要有三種:靜態(tài)表查詢方式,修改窗口用戶數(shù)據(jù)(USERDATA)方式,Thunk方式。其中Thunk方式是最直接、最高效的方式,本文主要討論Thunk方式。

      1 基本原理

      Thunk在程序設(shè)計領(lǐng)域被稱為形實轉(zhuǎn)換程序,其基本思想就是將若干連續(xù)存儲的數(shù)據(jù)直接解釋成代碼讓CPU執(zhí)行,本質(zhì)上相當(dāng)于直接使用機器語言編程。

      利用Thunk技術(shù)封裝窗口類的基本思路是:將一段精心設(shè)計的數(shù)據(jù)(實際是一些機器指令,以下將這段數(shù)據(jù)簡稱為Thunk數(shù)據(jù))解釋成Windows窗口處理函數(shù),同時將this指針存儲在這些數(shù)據(jù)中,從而達到傳遞this指針的目的。

      2 關(guān)鍵技術(shù)

      雖然理論上可以在Thunk數(shù)據(jù)中使用機器代碼完成對類的非靜態(tài)成員函數(shù)的調(diào)用,但是這需要完全模擬C++對類的非靜態(tài)成員函數(shù)的調(diào)用機制,而這種機制是比較復(fù)雜的,并且可能會在將來有所改變。為了盡量縮短Thunk數(shù)據(jù)代碼的長度,一種更好的方式是在Thunk數(shù)據(jù)代碼中調(diào)用另一個靜態(tài)的窗口處理函數(shù)(以下簡稱中轉(zhuǎn)處理函數(shù)),同時采用一種技術(shù)將this指針傳遞給該中轉(zhuǎn)處理函數(shù)。

      向中轉(zhuǎn)處理函數(shù)傳遞this指針的方式基本上有兩種:

      1) 在標(biāo)準(zhǔn)Windows窗口處理函數(shù)的參數(shù)列表中增加一個參數(shù)并將this指針傳遞給這個參數(shù),即使用非標(biāo)準(zhǔn)形式的窗口處理函數(shù),但這種形式會增加Thunk數(shù)據(jù)代碼的復(fù)雜度;

      2) 使用標(biāo)準(zhǔn)形式的窗口處理函數(shù)并將其參數(shù)列表中的某個參數(shù)修改為this指針值,這種方式將最大程度的降低Thunk數(shù)據(jù)代碼的復(fù)雜度,因此采用這種方式較好。

      Windows窗口處理函數(shù)的標(biāo)準(zhǔn)形式如下:

      代碼 1 WindowProc

      其參數(shù)列表中一共有4個參數(shù),除hwnd參數(shù)以外的其他3個參數(shù)都有其特定用途,唯有hwnd,它代表目標(biāo)窗口句柄,而這個句柄完全可以預(yù)先保存在類(對象)中然后通過this指針訪問它,因此可以在Thunk數(shù)據(jù)代碼調(diào)用中轉(zhuǎn)處理函數(shù)之前將hwnd參數(shù)用this指針值替換掉,從而達到傳遞this指針的目的。

      在中轉(zhuǎn)處理函數(shù)中,通過強制類型轉(zhuǎn)換,將hwnd參數(shù)硬性解釋成this指針,并通過該this指針調(diào)用類的非靜態(tài)窗口處理成員函數(shù),從而達到了讓W(xué)indows窗口處理回調(diào)機制調(diào)用窗口類非靜態(tài)成員函數(shù)的目的,至此就完成了窗口類的封裝。

      3 實現(xiàn)細(xì)節(jié)

      使用Thunk技術(shù)封裝窗口類的大體實現(xiàn)細(xì)節(jié)如下:

      在窗口類中定義一個THUNK結(jié)構(gòu)類型的成員變量_thunk及初始化該結(jié)構(gòu)的成員函數(shù)InitThunk();

      THUNK結(jié)構(gòu)實際是一段經(jīng)過仔細(xì)設(shè)計的機器代碼,當(dāng)將THUNK變量地址當(dāng)成函數(shù)地址來解釋并調(diào)用它時,將執(zhí)行這段代碼,在本例中就是要將其解釋成WNDPROC類型的指針,即Windows的窗口處理回調(diào)函數(shù),這樣在回調(diào)發(fā)生時THUNK代碼將被執(zhí)行。

      在窗口類中提供一個attach(HWND h)函數(shù),其將修改目標(biāo)窗口h的窗口處理函數(shù)地址,使其指向this->_thunk,在執(zhí)行這條語句之前應(yīng)先調(diào)用InitThunk()初始化_thunk成員,使其中保存this指針值,也就是說,每個窗口類對象中都會有一個含有對象地址信息(即this)的成員變量(即_thunk)。

      THUNK結(jié)構(gòu)中的其他代碼完成如下工作:將Windows調(diào)用THUNK數(shù)據(jù)代碼時傳遞過來的4個參數(shù)hwnd,uMsg,wParam,lParam中的hwnd替換成this指針值,然后調(diào)用中轉(zhuǎn)處理函數(shù)。由于Windows調(diào)用THUNK代碼時已經(jīng)將函數(shù)參數(shù)正確的入棧且中轉(zhuǎn)處理函數(shù)的調(diào)用方式及參數(shù)格式與標(biāo)準(zhǔn)Windows窗口處理函數(shù)相同,因此,調(diào)用中轉(zhuǎn)處理函數(shù)的代碼可簡單地使用一條跳轉(zhuǎn)指令直接跳轉(zhuǎn)到中轉(zhuǎn)處理函數(shù)的地址即可。需要注意的是,如果采用前面介紹的非標(biāo)準(zhǔn)形式的窗口處理函數(shù)來傳遞this指針,則需要增加修改堆棧指針、將this指針入棧等額外的操作,這無疑會增加THUNK數(shù)據(jù)代碼的復(fù)雜性。

      在窗口類中定義一個靜態(tài)的窗口處理函數(shù)stdProc()充當(dāng)中轉(zhuǎn)處理函數(shù)。在stdProc()中,通過強制類型轉(zhuǎn)換,將hwnd參數(shù)強制轉(zhuǎn)換為this指針并通過該this指針調(diào)用窗口類的非靜態(tài)窗口處理成員函數(shù),這樣就實現(xiàn)了讓W(xué)indows窗口處理回調(diào)函數(shù)調(diào)用窗口類非靜態(tài)成員函數(shù)的過程。

      通過上述方法封裝的窗口類,其每一個窗口對象都有不同的窗口處理函數(shù)地址,它指向this->_thunk成員變量。

      使用用THUNK技術(shù)封裝窗口類的本質(zhì)是:用窗口類的數(shù)據(jù)成員而不是函數(shù)成員“冒充”窗口處理函數(shù),從而攔截到窗口消息。

      4 借用ATL中的Thunk代碼

      實現(xiàn)Thunk技術(shù)的關(guān)鍵是給出Thunk數(shù)據(jù)的對應(yīng)機器代碼。雖然可以通過查詢文檔等方式獲得相應(yīng)機器代碼,但還有更簡便的方式:利用ATL模板庫中的Thunk代碼。

      ATL[3]是Microsoft公司繼MFC后推出的用于編寫COM組件的模板庫,它提供了很多模板類以方便COM組件的編寫,其中就包含對Windows窗口類的封裝。ATL封裝窗口類的方法中使用的就是Thunk技術(shù)。

      ATL提供了4個與Thunk有關(guān)的類:_stdcallthunk,CDynamicStdCallThunk,CStdCallThunk,CWndProcThunk,其中前3個類位于頭文件中,最后一個位于頭文件中。

      _stdcallthunk是ATL Thunk的基本實現(xiàn),它包含了全部Thunk數(shù)據(jù)代碼,其定義如下:

      #pragma pack(push,1)

      struct _stdcallthunk{

      DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)

      DWORD m_this; //

      BYTE m_jmp; // jmp WndProc

      DWORD m_relproc; // relative jmp

      BOOL Init(DWORD_PTR proc, void* pThis){

      m_mov = 0x042444C7; //C7 44 24 0C

      m_this = PtrToUlong(pThis);

      m_jmp = 0xe9;

      m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));

      // write block from data cache and

      // flush from instruction cache

      FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));

      return TRUE;

      }

      //some thunks will dynamically allocate the memory for the code

      void* GetCodeAddress(){ return this; }

      void* operator new(size_t){ return __AllocStdCallThunk(); }

      void operator delete(void* pThunk){ __FreeStdCallThunk(pThunk); }

      };

      #pragma pack(pop)

      代碼 2 _stdcallthunk

      _stdcallthunk中含有機器指令,因此是硬件相關(guān)的,上述是在x86平臺下的定義,ATL還提供了幾個在其他平臺下的_stdcallthunk定義,有興趣的讀者可以查閱相關(guān)代碼。限于篇幅,本文不對_stdcallthunk的機器代碼做過多解釋,因為那并不十分重要,在此只給出_stdcallthunk的使用方式:首先調(diào)用Init(DWORD_PTR proc, void* pThis)函數(shù)初始化該結(jié)構(gòu),其中傳給proc參數(shù)的值就是窗口類中定義的靜態(tài)中轉(zhuǎn)處理函數(shù)地址,傳給pThis參數(shù)的值就是窗口類對象的this指針值。初始化成功后,就可以調(diào)用GetCodeAddress()函數(shù)獲取窗口處理函數(shù)的地址(正如代碼中給出的那樣,它其實就是_stdcallthunk結(jié)構(gòu)的地址)并將目標(biāo)窗口的窗口處理函數(shù)地址修改為GetCodeAddress()的返回值。

      CDynamicStdCallThunk是_stdcallthunk的動態(tài)分配版本,就像_stdcallthunk定義中的注釋說的那樣:某些平臺下,可執(zhí)行代碼所在的內(nèi)存必須使用動態(tài)分配方式獲取,即代碼必須位于堆中而不能位于棧中,x86平臺下的Windows系統(tǒng)中即是如此,因此,我們不能直接使用_stdcallthunk結(jié)構(gòu)。CDynamicStdCallThunk的定義如下:

      #pragma pack(push,8)

      class CDynamicStdCallThunk

      {

      public:

      _stdcallthunk *pThunk;

      CDynamicStdCallThunk(){ pThunk = NULL; }

      ~CDynamicStdCallThunk(){

      if (pThunk){

      delete pThunk;

      }

      }

      BOOL Init(DWORD_PTR proc, void *pThis){

      if (pThunk == NULL){

      pThunk = new _stdcallthunk;

      if (pThunk == NULL){

      return FALSE;

      }

      }

      return pThunk->Init(proc, pThis);

      }

      void* GetCodeAddress(){ return pThunk->GetCodeAddress(); }

      };

      #pragma pack(pop)

      代碼 3 CDynamicStdCallThunk

      CDynamicStdCallThunk的使用方式與stdcallthunk類似,在此不做贅述。

      CStdCallThunk根據(jù)平臺的不同,可能是對CDynamicStdCallThunk的包裝,也可能是對_stdcallthunk的包裝:

      typedef CDynamicStdCallThunk CStdCallThunk;

      typedef _stdcallthunk CStdCallThunk;

      代碼 4 CStdCallThunk

      CWndProcThunk是ATL窗口類中實際使用的結(jié)構(gòu)(類),它除了對CStdCallThunk的成員函數(shù)簽名進行了類型上的限定外,還定義了一個_AtlCreateWndData數(shù)據(jù)成員,這個成員在本文編寫的窗口類中用不到,CWndProcThunk的定義如下:

      class CWndProcThunk

      {

      public:

      _AtlCreateWndData cd;

      CStdCallThunk thunk;

      BOOL Init(WNDPROC proc, void* pThis){ return thunk.Init((DWORD_PTR)proc, pThis); }

      WNDPROC GetWNDPROC(){ return (WNDPROC)thunk.GetCodeAddress(); }

      };

      代碼 5 CWndProcThunk

      可以借用ATL提供的4個Thunk結(jié)構(gòu)中的任意一個來編寫我們的窗口類,但很明顯,使用CWndProcThunk無論在兼容性方面還是在可維護性方面都是最好的,因此,本文使用CWndProcThunk結(jié)構(gòu)封裝窗口類。

      這里需要說明一下,ATL已經(jīng)提供了一個封裝好的窗口類CWindowImpl,之所以借用ATL的Thunk結(jié)構(gòu)來封裝一個新的窗口類而不是直接使用ATL的窗口類是因為:ATL的窗口類額外引入了一些我們不需要的成員,并且其窗口類的行為未必完全滿足某些特定需要,又或者對于某些要求精簡化的程序來說,CWindowImpl過于復(fù)雜了。

      5 結(jié)論

      采用Thunk技術(shù)封裝的窗口類無論在代碼規(guī)模還是執(zhí)行效率上都有很大優(yōu)勢,借用ATL的Thunk結(jié)構(gòu)又可以進一步簡化代碼編寫工作,同時代碼的兼容性和可維護性也得到了很大的提高。

      參考文獻:

      [1] Stanley B.Lippman,Josee Lajoie. C++ Primer[M].潘愛民,張麗,譯.北京:中國電力出版社,2004.

      [2] Charles Petzold. Windows程序設(shè)計[M].方敏,張勝,梁路平,譯.北京:清華大學(xué)出版社,2010.

      [3] BRENT RECTOR,CHRIS SELLS.深入解析ATL[M].潘愛民,新語,譯.北京:中國電力出版社,2001.

      改则县| 扶余县| 萍乡市| 珠海市| 灵寿县| 乳山市| 黄大仙区| 崇信县| 石家庄市| 惠安县| 沈丘县| 荃湾区| 女性| 喀什市| 台中县| 锦州市| 鸡西市| 海城市| 将乐县| 兰溪市| 延寿县| 新巴尔虎右旗| 五大连池市| 漠河县| 高邮市| 阳原县| 大理市| 陈巴尔虎旗| 抚宁县| 资阳市| 漳平市| 巴马| 宜章县| 葵青区| 渭南市| 文安县| 儋州市| 南丰县| 乐东| 荣昌县| 康保县|