面向?qū)ο笫莻€(gè)好東西,用接近世界的方式抽象程序世界,直觀。 全局函數(shù)(或許我應(yīng)該特指Windows API)也是好東西,要什么調(diào)什么,毫不含糊. 那么,當(dāng)他們走到一起,矛盾就產(chǎn)生 類時(shí)刻保護(hù)著自己的成員,以至于為每一個(gè)方法加入一個(gè)指向自己的指針. 比如有以下類
1class TestClass()
2{ 3 void Func(); 4};
則Func被編譯器安插了this以針,以便Func內(nèi)部可以訪問類TestClass的成員變量,即Func變?yōu)槿缦聵幼?/p>
1void Func(TestClass* this);
在實(shí)際的開發(fā)中,使用API時(shí)常常會(huì)要求我們提供回調(diào)函數(shù),比如SetTimer,我們需要設(shè)置向這個(gè)API提供一個(gè)如下類型的函數(shù)指針:
1typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
假如我們有如下類 1class TestClass()
2{ 3 void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ) 4 { 5 //do something 6 } 7}; 8 9
并希望將成員函數(shù)
1void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
1UINT_PTR
2WINAPI 3SetTimer( 4 __in_opt HWND hWnd, 5 __in UINT_PTR nIDEvent, 6 __in UINT uElapse, 7 __in_opt TIMERPROC lpTimerFunc); 8 9
的第四個(gè)參數(shù),以便定時(shí)器的時(shí)間到時(shí),我們的類成員函數(shù)TestFunc:OnTimerProc被調(diào)用。 根據(jù)最前面對(duì)Func的分析,在編譯時(shí),OnTimerProc會(huì)被安插this指針,變成如下形式:
1void OnTimeProc(TestClass *this, HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
很顯然,我們無法直接設(shè)置。 那我們應(yīng)該怎么做呢,在完成這個(gè)任務(wù)之前,讓我們先看一下一個(gè)稍簡(jiǎn)單一點(diǎn)的例子,用以說明Thunk原理。 Thunk的原理其實(shí)說起來很簡(jiǎn)單:巧妙的將數(shù)據(jù)段的幾個(gè)字節(jié)的數(shù)據(jù)設(shè)為特殊的值,然后告訴系統(tǒng),這幾個(gè)字節(jié)的數(shù)據(jù)是代碼(即將一個(gè)函數(shù)指針指向這幾個(gè)字節(jié)的第一個(gè)字節(jié)),讓系統(tǒng)來執(zhí)行。 這樣說起來就很簡(jiǎn)單. 相信對(duì)于后一個(gè)操作:將一個(gè)函數(shù)指針指向這幾個(gè)字節(jié)的第一個(gè)字節(jié)我們都應(yīng)該會(huì): 比如有結(jié)構(gòu)體:
1typedef struct thunk
2{ 3 DWORD dwMovEsp; 4 DWORD dwThis; 5 BYTE bJmp; 6 DWORD dwRealProc; 7 8}THUNK; 9
函數(shù)指針:
1typedef void (*FUNC)(DWORD dwThis);
則如下代碼將一個(gè)thunk的結(jié)構(gòu)體強(qiáng)轉(zhuǎn)為FUNC型的函數(shù)指針:
1THUNK testThunk;
2 3FUNC fun = (FUNC)&testThunk; 4 5fun(NULL);//先設(shè)為NULL 6
這樣,系統(tǒng)便會(huì)把testThunk所指向的內(nèi)存加載到緩沖中。 現(xiàn)在的總是是將這個(gè)結(jié)構(gòu)體設(shè)為多少比較好? 在x86 指令集中,我們可以查到: 匯編指令JMP為0xe9 所以,我們寫下如下函數(shù)用于設(shè)置這個(gè)結(jié)構(gòu)體的值:
1void Init(DWORD proc,void* pThis)
2 { 3 dwMovEsp = 0x042444C7; //C7 44 24 04 4 dwThis = (DWORD)pThis; 5 bJmp = 0xe9; 6 dwRealProc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(thunk))); 7 FlushInstructionCache(GetCurrentProcess(),this,sizeof(thunk)); 8 }
前兩行用于將pThis指針壓棧,接下來的兩句用于設(shè)置跳轉(zhuǎn)的相對(duì)地址。最后一個(gè)是更新緩存(說實(shí)話,我個(gè)人覺得這句在這種情況下是可有可無的,但也可能是我認(rèn)識(shí)不夠深,望指教)。 整個(gè)代碼如下: Code
測(cè)試成功,接下來是將thunk技術(shù)應(yīng)用到實(shí)際中,就是一開始提出的問題。 首先,要使用定時(shí)器的功能,肯定要調(diào)用API:SetTimer,而調(diào)用這個(gè)API需要一個(gè)如下簽名的函數(shù)指針:typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);因此,我們要做的,就是利用Thunk技術(shù),讓這個(gè)回調(diào)函數(shù)調(diào)用我們的類的成員方法。 我們可以用一個(gè)代理類來完成這一系列的工作,然后我們的真正的業(yè)務(wù)邏輯類就繼承自這個(gè)代理類。 現(xiàn)在想想這個(gè)代理類要完成這個(gè)任務(wù)需要那些數(shù)據(jù)? 首先,他要知道當(dāng)他被API回調(diào)時(shí),他應(yīng)該調(diào)用哪一個(gè)類的成員方法,類的面員方法的函數(shù)指針時(shí)需要指定類類型。如下所示:
1void (Base:: * )( HWND , UINT , UINT , DWORD );
看到這里,相信任何一個(gè)初級(jí)的剛?cè)腴T的c++程序員都可以快速的寫下以下類:
1class SimpleTest;
2class SimpleTimerAdapter 3{ 4public: 5 CALLBACKThunk thunk; 6 typedef void (SimpleTest::*func)(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ); 7 typedef func MemberCallBackType; 8 MemberCallBackType mTimerProc; 9 10 void Init(TIMERPROC proc, void* pThis,int nPos = 0) 11 { 12 assert(pThis != NULL); 13 if(pThis) 14 { 15 thunk.m_mov = 0x042444C7; //C7 44 24 04, here 04 is first param ,08 is second 16 thunk.m_this = (DWORD)pThis; 17 thunk.m_jmp = 0xe9; 18 thunk.m_relproc = (int)proc - ((int)this + sizeof(CALLBACKThunk)); 19 } 20 } 21 22 TIMERPROC MakeCallback(MemberCallBackType lpfn,void* pThis, int nPos = 0) 23 { 24 assert(pThis); 25 if (pThis) 26 { 27 Init(DefaultCallBackProc, pThis ,nPos); 28 mTimerProc = lpfn; 29 return (TIMERPROC)&thunk; 30 } 31 return NULL; 32 } 33 34 UINT_PTR SetTimer(UINT uElapse, MemberCallBackType lpTimerFunc) 35 { 36 return ::SetTimer(NULL, 0, uElapse, MakeCallback(lpTimerFunc,this)); 37 } 38 39 BOOL KillTimer(UINT_PTR uIDEvent) 40 { 41 return ::KillTimer(NULL, uIDEvent); 42 } 43 44 static void CALLBACK DefaultCallBackProc( HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ) 45 { 46 (BaseType(hwnd)->*MemberFuncType(hwnd))(0, uMsg, idEvent, dwTime); 47 } 48 49 static SimpleTest* BaseType(void* pThis) 50 { 51 return reinterpret_cast<SimpleTest*>(pThis); 52 } 53 54 template <class T> 55 static MemberCallBackType MemberFuncType(T pThis) 56 { 57 return reinterpret_cast<SimpleTest*>(pThis)->mTimerProc; 58 } 59}; 60 61 62 63 64 65class SimpleTest : public SimpleTimerAdapter 66{ 67public: 68 bool mQuit; 69 70 void TimerProc2(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ) 71 { 72 mQuit = true; 73 KillTimer( idEvent); 74 printf("good! %d\n", idEvent); 75 } 76}; 77 78 79 80
然后在MAIN中寫下測(cè)試代碼:
1int main(void)
2{ 3 SimpleTest a; 4 a.mQuit = false; 5 SetTimer(NULL, 0, 1000, a.MakeCallback(&SimpleTest::TimerProc2,&a)); 6 7 MSG msg; 8 while(!a.mQuit && GetMessage(&msg, 0, 0, 0) ) 9 { 10 printf("before dispatch!\n"); 11 DispatchMessage(&msg); 12 } 13 14 system("pause"); 15 return 0; 16} 17 18
以上方法確實(shí)可以完成任務(wù),但僅限于完成這一個(gè)任務(wù)而已, 甚至,在一個(gè)類中SimpleTimeAdapter中寫出了這樣的代碼:
Code
我寫出這段代碼只是為了更清楚的顯示代理類是如何工作的,除此之外,以上代碼,沒有任何作用,為真正的純垃圾代碼 為了抽象出一個(gè)中間代理類,我們需要用到模板,對(duì)于上面提到的定義問題,用模板可以很輕松的解決。同時(shí),把最基本的內(nèi)容從代理類中抽象出來。于是得以以下三個(gè)類:
1/*
2* class Base,最終的功能類,目地的要跳轉(zhuǎn)到Base的成員函數(shù)中去 3* class Impl,中間類,界于Adapt與Base之間 4* MemberCallBackType Base的成員函數(shù) 5* CallBackType,我們給API的回調(diào)函數(shù) 6*/ 7template <class Base, class Impl, class MemberCallBackType, class CallBackType> 8class CallBackAdapter 9{ 10protected: 11 typedef CallBackAdapter<Base, Impl, MemberCallBackType, CallBackType> SelfType; 12 typedef MemberCallBackType BaseMemberCallBackType; 13 14 CALLBACKThunk thunk; 15 16 void Init(CallBackType proc, SelfType* pThis,int nPos = 0) 17 { 18 thunk.m_mov = 0x042444C7; //C7 44 24 04, here 04 is first param ,08 is second 19 thunk.m_this = (DWORD)pThis; 20 thunk.m_jmp = 0xe9; 21 thunk.m_relproc = (int)proc - ((int)this + sizeof(CALLBACKThunk)); 22 } 23 24 CallBackType _CallBackProcAddress(void){ 25 return (CallBackType)&thunk; 26 } 27public: 28 template <class T> 29 static Base* BaseType(T pThis){ 30 return reinterpret_cast<Base*>(pThis); 31 } 32 33 template <class T> 34 static MemberCallBackType MemberFuncType(T pThis){ 35 return reinterpret_cast<SelfType*>(pThis)->mTimerProc; 36 } 37 38 MemberCallBackType mTimerProc; 39 40 operator CallBackType(){ 41 42 Init(&Impl::DefaultCallBackProc, this); 43 mTimerProc = &Base::TimerProc; 44 return (CallBackType)&thunk; 45 } 46 CallBackType MakeCallback(MemberCallBackType lpfn,int nPos = 0){ 47 48 Init(&Impl::DefaultCallBackProc, this,nPos); 49 mTimerProc = lpfn; 50 return (CallBackType)&thunk; 51 } 52}; 53 54 55 56 57 58 59 60template <class Base> 61class TimerAdapter : public CallBackAdapter< 62 Base, 63 TimerAdapter<Base>, 64 void (Base:: * )( HWND , UINT , UINT , DWORD ), 65 void (CALLBACK *)( HWND , UINT , UINT , DWORD )> 66{ 67public: 68 typedef typename TimerAdapter<Base>::BaseMemberCallBackType MemCallBackType; 69 70 UINT_PTR SetTimer(UINT uElapse, MemCallBackType lpTimerFunc) 71 { 72 return ::SetTimer(NULL, 0, uElapse, MakeCallBackProc(lpTimerFunc)); 73 } 74 75 BOOL KillTimer(UINT_PTR uIDEvent) 76 { 77 return ::KillTimer(NULL, uIDEvent); 78 } 79 80 static void CALLBACK DefaultCallBackProc( HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ) 81 { 82 (BaseType(hwnd)->*MemberFuncType(hwnd))(0, uMsg, idEvent, dwTime); 83 //(Base*)(hwnd)->*(reinterpret_cast<Base*>(hwnd)->mTimerProc)(0, uMsg, idEvent, dwTime); 84 } 85 86}; 87 88 測(cè)試代碼如下:
1int main(void)
2{ 3 Test a; 4 printf("timer id is %d", a.SetTimer(100, &Test::TimerProc2)); 5 a.mQuit = false; 6 SetTimer(NULL, 0, 100, a.MakeCallback(&Test::TimerProc2)); 7 8 //SimpleTest a; 9 //a.mQuit = false; 10 //SetTimer(NULL, 0, 1000, a.MakeCallback(&SimpleTest::TimerProc2,&a)); 11 12 MSG msg; 13 while(!a.mQuit && GetMessage(&msg, 0, 0, 0) ) 14 { 15 printf("before dispatch!\n"); 16 DispatchMessage(&msg); 17 } 18 19 system("pause"); 20 return 0; 21} 22
參考: ATL Under the HOOK Part 5 : http://www./KB/atl/atl_underthehood_5.aspx 還有一篇也是CodeProject上的,但由于看文章的時(shí)間太久了,今天再去找時(shí)沒有找到。 |
|