面向对象是个好东西,用接近世界的方式抽象程序世界,直观。 全局函数(或许我应该特指Windows API)也是好东西,要什么调什么,毫不含糊. 那么,当他们走到一起,矛盾就产生 类时刻保护着自己的成员,以至于为每一个方法加入一个指向自己的指针. 比如有以下类
1class TestClass()
2{ 3 void Func(); 4};
则Func被编译器安插了this以针,以便Func内部可以访问类TestClass的成员变量,即Func变为如下样子
1void Func(TestClass* this);
在实际的开发中,使用API时常常会要求我们提供回调函数,比如SetTimer,我们需要设置向这个API提供一个如下类型的函数指针:
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
并希望将成员函数
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
的第四个参数,以便定时器的时间到时,我们的类成员函数TestFunc:OnTimerProc被调用。 根据最前面对Func的分析,在编译时,OnTimerProc会被安插this指针,变成如下形式:
1void OnTimeProc(TestClass *this, HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
很显然,我们无法直接设置。 那我们应该怎么做呢,在完成这个任务之前,让我们先看一下一个稍简单一点的例子,用以说明Thunk原理。 Thunk的原理其实说起来很简单:巧妙的将数据段的几个字节的数据设为特殊的值,然后告诉系统,这几个字节的数据是代码(即将一个函数指针指向这几个字节的第一个字节),让系统来执行。 这样说起来就很简单. 相信对于后一个操作:将一个函数指针指向这几个字节的第一个字节我们都应该会: 比如有结构体:
1typedef struct thunk
2{ 3 DWORD dwMovEsp; 4 DWORD dwThis; 5 BYTE bJmp; 6 DWORD dwRealProc; 7 8}THUNK; 9
函数指针:
1typedef void (*FUNC)(DWORD dwThis);
则如下代码将一个thunk的结构体强转为FUNC型的函数指针:
1THUNK testThunk;
2 3FUNC fun = (FUNC)&testThunk; 4 5fun(NULL);//先设为NULL 6
这样,系统便会把testThunk所指向的内存加载到缓冲中。 现在的总是是将这个结构体设为多少比较好? 在x86 指令集中,我们可以查到: 汇编指令JMP为0xe9 所以,我们写下如下函数用于设置这个结构体的值:
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指针压栈,接下来的两句用于设置跳转的相对地址。最后一个是更新缓存(说实话,我个人觉得这句在这种情况下是可有可无的,但也可能是我认识不够深,望指教)。 整个代码如下: Code
测试成功,接下来是将thunk技术应用到实际中,就是一开始提出的问题。 首先,要使用定时器的功能,肯定要调用API:SetTimer,而调用这个API需要一个如下签名的函数指针:typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);因此,我们要做的,就是利用Thunk技术,让这个回调函数调用我们的类的成员方法。 我们可以用一个代理类来完成这一系列的工作,然后我们的真正的业务逻辑类就继承自这个代理类。 现在想想这个代理类要完成这个任务需要那些数据? 首先,他要知道当他被API回调时,他应该调用哪一个类的成员方法,类的面员方法的函数指针时需要指定类类型。如下所示:
1void (Base:: * )( HWND , UINT , UINT , DWORD );
看到这里,相信任何一个初级的刚入门的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中写下测试代码:
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
以上方法确实可以完成任务,但仅限于完成这一个任务而已, 甚至,在一个类中SimpleTimeAdapter中写出了这样的代码:
Code
我写出这段代码只是为了更清楚的显示代理类是如何工作的,除此之外,以上代码,没有任何作用,为真正的纯垃圾代码 为了抽象出一个中间代理类,我们需要用到模板,对于上面提到的定义问题,用模板可以很轻松的解决。同时,把最基本的内容从代理类中抽象出来。于是得以以下三个类:
1/*
2* class Base,最终的功能类,目地的要跳转到Base的成员函数中去 3* class Impl,中间类,界于Adapt与Base之间 4* MemberCallBackType Base的成员函数 5* CallBackType,我们给API的回调函数 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 测试代码如下:
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上的,但由于看文章的时间太久了,今天再去找时没有找到。 |
|