六月婷婷综合激情-六月婷婷综合-六月婷婷在线观看-六月婷婷在线-亚洲黄色在线网站-亚洲黄色在线观看网站

明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

thunk技術完成窗口類的封裝

[摘要]MFC功能已經非常強大,自己做界面庫也許沒什么意思,但是這個過程中卻能學到很多東西。比如說:窗口類的封裝,從全局窗口消息處理到窗口對象消息處理的映射方法: 對界面進行封裝,一般都是一個窗口一個類,比如實現一個最基本的窗口類CMyWnd,你一定會把窗口過程作為這個類的成員函數,但是使用WINA...
MFC功能已經非常強大,自己做界面庫也許沒什么意思,但是這個過程中卻能學到很多東西。比如說:

窗口類的封裝,從全局窗口消息處理到窗口對象消息處理的映射方法:

    對界面進行封裝,一般都是一個窗口一個類,比如實現一個最基本的窗口類CMyWnd,你一定會把窗口過程作為這個類的成員函數,但是使用WINAPI創建窗口時必須注冊類WNDCLASS,里面有個成員數據lpfnWndProc需要WNDPROC的函數指針,一般想法就是把窗口類的消息處理函數指針傳過去,但是類成員函數除非是靜態的,否則無法轉換到WNDPROC,而全局的消息處理函數又無法得到窗口類對象的指針。這里有幾種解決辦法:

    一種解決方法是用窗口列表,開一個結構數組,窗口類對象創建窗口的時候把窗口HWND和this指針放入數組,全局消息處理函數遍歷數組,利用HWND找出this指針,然后定位到對象內部的消息處理函數。這種方法查找對象的時間會隨著窗口個數的增多而增長。

    另一種方法比較聰明一點,WNDCLASS里面有個成員數據cbWndExtra一般是不用的,利用這點,注冊類時給該成員數據賦值,這樣窗口創建時系統會根據該值開辟一塊內存與窗口綁定,這時把創建的窗口類的指針放到該塊內存,那么在靜態的窗口消息循環函數就能利用GetWindowLong(hWnd,GWL_USERDATA)取出該指針,return (CMyWnd*)->WindowProc(...),這樣就不用遍歷窗口了。但是這樣一來就有個致命弱點,對窗口不能調用SetWindowLong(hWnd,GWL_USERDATA,數據),否則就會導致程序崩潰。幸好這個函數(特定這幾個參數)是調用幾率極低的,對于窗口,由于創建窗口都是調用窗口類的Create函數,不用手工注冊WNDCLASS類,也就不會調用SetWindowLong函數。但是畢竟缺乏安全性,而且當一秒鐘內處理的窗口消息很多時,這種查找速度也可能不夠快。

    還有一種就是比較完美的解決辦法,稱之為thunk技術。thunk是一組動態生成的ASM指令,它記錄了窗口類對象的this指針,并且這組指令可以當作函數,既也可以是窗口過程來使用。thunk先把窗口對象this指針記錄下來,然后轉向到靜態stdProc回調函數,轉向之前先記錄HWND,然后把堆棧里HWND的內容替換為this指針,這樣在stdProc里就可以從HWND取回對象指針,定位到WindowProc了。

    我們先來看看窗口過程函數定義:
    
    LRESULT WINAPI WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
    
    其實當我們的窗口類CMyWnd創建窗口的時候,窗口句柄是可以得到并且作為成員數據保存,如此一來,第一個參數hWnd是可以不要的,因為可以通過this->m_hWnd得到,我們可以在這里做手腳,hWnd其實質是一個指針,如果把這個參數替換為窗口類對象的this指針,那么我們不就可以通過(CMyWnd*)hWnd->WindowProc轉到窗口類內部的窗口過程了嗎?但是窗口過程是系統調用的,怎么能把hWnd替換掉呢?我們先來看看系統調用這個函數時的堆棧情況:

系統調用m_thunk時的堆棧:
ret HWND MSG WPARAM LPARAM
-------------------------------------------
棧頂                                              棧底

系統把參數從右到左依次壓棧,最后把返回地址壓棧,我們只要在系統調用窗口過程時修改堆棧,把其中的hWnd參數替換掉就行了。這時thunk技術就有用武之地了,我們先定義一個結構:

#pragma pack(push,1) //該結構必須以字節對齊
struct Thunk {
BYTE    Call;
int    Offset;
WNDPROC   Proc;
BYTE    Code[5];
CMyWnd*   Window;
BYTE    Jmp;
BYTE    ECX;
};
#pragma pack(pop)

類定義:
class CMyWnd
{
public:
   BOOL Create(...);
   LRESULT WINAPI WindowProc(UINT,WPARAM,LPARAM);
   static LRESULT WINAPI InitProc(HWND,UINT,WPARAM,LPARAM);
   static LRESULT WINAPI stdProc(HWND,UINT,WPARAM,LPARAM);
   WNDPROC CreateThunk();
   WNDPROC GetThunk(){return m_thunk}
   ...

private:
   WNDPROC m_thunk
}

在創建窗口的時候把窗口過程設定為this->m_thunk,m_thunk的類型是WNDPROC,因此是完全合法的,當然這個m_thunk還沒有初始化,在創建窗口前必須初始化:

WNDPROC CMyWnd::CreateThunk()
{
   Thunk*  thunk = new Thunk;

///////////////////////////////////////////////
//
//系統調用m_thunk時的堆棧:
//ret HWND MSG WPARAM LPARAM
//-------------------------------------------
//棧頂                                             棧底
///////////////////////////////////////////////

//call Offset
//調用code[0],call執行時會把下一條指令壓棧,即把Proc壓棧
thunk->Call = 0xE8;        // call [rel]32
thunk->Offset = (size_t)&(((Thunk*)0)->Code)-(size_t)&(((Thunk*)0)->Proc);  // 偏移量,跳過Proc到Code[0]
thunk->Proc = CMyWnd::stdProc;  //靜態窗口過程

//pop ecx,Proc已壓棧,彈出Proc到ecx
thunk->Code[0] = 0x59;  //pop ecx

//mov dword ptr [esp+0x4],this
//Proc已彈出,棧頂是返回地址,緊接著就是HWND了。
//[esp+0x4]就是HWND
thunk->Code[1] = 0xC7;  // mov
thunk->Code[2] = 0x44;  // dword ptr
thunk->Code[3] = 0x24;  // disp8[esp]
thunk->Code[4] = 0x04;  // +4
thunk->Window = this;

//偷梁換柱成功!跳轉到Proc
//jmp [ecx]
thunk->Jmp = 0xFF;     // jmp [r/m]32
thunk->ECX = 0x21;     // [ecx]

m_thunk = (WNDPROC)thunk;
return m_thunk;
}

這樣m_thunk雖然是一個結構,但其數據是一段可執行的代碼,而其類型又是WNDPROC,系統就會忠實地按窗口過程規則調用這段代碼,m_thunk就把Window字段里記錄的this指針替換掉堆棧中的hWnd參數,然后跳轉到靜態的stdProc:

//本回調函數的HWND調用之前已由m_thunk替換為對象指針
LRESULT WINAPI CMyWnd::stdProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{
CMyWnd* w = (CMyWnd*)hWnd;

  return w->WindowProc(uMsg,wParam,lParam);
}

這樣就把窗口過程轉向到了類成員函數WindowProc,當然這樣還有一個問題,就是窗口句柄hWnd還沒來得及記錄,因此一開始的窗口過程應該先定位到靜態的InitProc,CreateWindow的時候給最后一個參數,即初始化參數賦值為this指針:

CreateWindowEx(
        dwExStyle,
                  szClass,
                  szTitle,
                  dwStyle,
                  x,
                  y,
                  width,
                  height,
                  hParentWnd,
                  hMenu,
                  hInst,
                  this            //初始化參數
                  );,

在InitProc里面取出該指針:

LRESULT WINAPI CMyWnd::InitProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{   
if(uMsg == WM_NCCREATE)
{
  CMyWnd *w = NULL;
  w = (CMyWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;
  if(w)
  {
   //記錄hWnd
   w->m_hWnd = hWnd;
   
   //改變窗口過程為m_thunk
   SetWindowLong(hWnd,GWL_WNDPROC,(LONG)w-CreateThunk());
   return (*(WNDPROC)(w->GetThunk()))(hWnd,uMsg,wParam,lParam);   
  }
}
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

這樣就大功告成。

窗口過程轉發流程:

    假設已建立CMyWnd類的窗口對象 CMyWnd *window,初始化完畢后調用window->Create,這時Create的窗口其窗口過程函數是靜態CMyWnd::InitWndProc

  

InitWndProc 實現功能:window->Create創建窗口時已把對象this指針放入窗口初始化參數中,在此過程的WM_NCCREATE消息中把this指針取出來:CMyWnd *w = (CMyWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;記錄HWND:w->m_hWnd = hWnd,然后設置窗口過程為w->m_thunk(thunk是一個WNDPROC類型的成員數據,所以可以設置)
└→ window->m_thunk 實現功能:跳轉到靜態CMyWnd::stdProc,在此之前替換系統的調用參數HWND為this指針
└→ stdProc 實現功能:把HWND轉換為窗口類指針:
CMyWnd *w = (CMyWnd*)hWnd;
return w->WindowProc(uMsg,wParam,lParam)
  └→  window->WindowProc 實現功能:執行實際的消息處理,窗口句柄已保存在m_hWnd 


主站蜘蛛池模板: 啪啪免费看视频 | 日本免费一区二区三区a区 日本免费一二三区 | 午夜人屠h精品全集 | 日韩特黄特色大片免费视频 | 色456| 日韩国产有码在线观看视频 | 日本特黄特色aa大片免费 | 欧美视频亚洲视频 | 亚洲韩国日本欧美一区二区三区 | 亚洲成a人片在线观看中文app | 污污的视频在线观看 | 日本黄网站高清色大全 | 午夜专区| 天天噜噜揉揉狠狠夜夜 | 亚洲综合狠狠 | 婷婷伊人网 | 日韩3级| 青青在线精品2022国产 | 四虎永久免费最新在线 | 亚洲国产精品综合欧美 | 日本视频三区 | 五月天激情在线 | 欧美午夜视频一区二区 | 日韩久久网 | 日韩毛片免费视频一级特黄 | 日韩精品视频在线播放 | 亚洲 丝袜 制服 欧美 另类 | 午夜h视频| 天堂24| 四川一级护士一级毛片 | 日本高清视频一区二区三区 | 午夜一级精品免费毛片 | 欧美一级乱理片免费观看 | 婷婷六月久久综合丁香可观看 | 亚洲成a人片77777老司机 | 日本在线视频网站www色下载 | 日本欧美一区二区三区片 | 亚洲主播自拍 | 婷婷六月激情 | 婷婷啪啪| 天天天天做夜夜夜做 |