也说说Thunk
面向对象是个好东西,用接近世界的方式抽象程序世界,直观。
全局函数(或许我应该特指Windows API)也是好东西,要什么调什么,毫不含糊.
那么,当他们走到一起,矛盾就产生
类时刻保护着自己的成员,以至于为每一个方法加入一个指向自己的指针.
比如有以下类
class TestClass()2
{3
void Func();4
};
则Func被编译器安插了this以针,以便Func内部可以访问类TestClass的成员变量,即Func变为如下样子
void Func(TestClass* this);
在实际的开发中,使用API时常常会要求我们提供回调函数,比如SetTimer,我们需要设置向这个API提供一个如下类型的函数指针:
typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
假如我们有如下类
class TestClass()2
{3
void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime )4
{5
//do something6
}7
}; 8

9

并希望将成员函数
void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
设为API:
UINT_PTR2
WINAPI3
SetTimer(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指针,变成如下形式:
void OnTimeProc(TestClass *this, HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
很显然,我们无法直接设置。
那我们应该怎么做呢,在完成这个任务之前,让我们先看一下一个稍简单一点的例子,用以说明Thunk原理。
Thunk的原理其实说起来很简单:巧妙的将数据段的几个字节的数据设为特殊的值,然后告诉系统,这几个字节的数据是代码(即将一个函数指针指向这几个字节的第一个字节),让系统来执行。
这样说起来就很简单.
相信对于后一个操作:将一个函数指针指向这几个字节的第一个字节我们都应该会:
比如有结构体:
typedef struct thunk2
{3
DWORD dwMovEsp;4
DWORD dwThis;5
BYTE bJmp;6
DWORD dwRealProc; 7

8
}THUNK; 9

函数指针:
typedef void (*FUNC)(DWORD dwThis);
则如下代码将一个thunk的结构体强转为FUNC型的函数指针:
THUNK testThunk; 2

3
FUNC fun = (FUNC)&testThunk; 4

5
fun(NULL);//先设为NULL 6

这样,系统便会把testThunk所指向的内存加载到缓冲中。
现在的总是是将这个结构体设为多少比较好?
在x86 指令集中,我们可以查到:
汇编指令JMP为0xe9
所以,我们写下如下函数用于设置这个结构体的值:
void Init(DWORD proc,void* pThis)2
{3
dwMovEsp = 0x042444C7; //C7 44 24 044
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指针压栈,接下来的两句用于设置跳转的相对地址。最后一个是更新缓存(说实话,我个人觉得这句在这种情况下是可有可无的,但也可能是我认识不够深,望指教)。
整个代码如下:
测试成功,接下来是将thunk技术应用到实际中,就是一开始提出的问题。
首先,要使用定时器的功能,肯定要调用API:SetTimer,而调用这个API需要一个如下签名的函数指针:typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);因此,我们要做的,就是利用Thunk技术,让这个回调函数调用我们的类的成员方法。
我们可以用一个代理类来完成这一系列的工作,然后我们的真正的业务逻辑类就继承自这个代理类。
现在想想这个代理类要完成这个任务需要那些数据?
首先,他要知道当他被API回调时,他应该调用哪一个类的成员方法,类的面员方法的函数指针时需要指定类类型。如下所示:
void (Base:: * )( HWND , UINT , UINT , DWORD );
看到这里,相信任何一个初级的刚入门的c++程序员都可以快速的写下以下类:
class SimpleTest;2
class SimpleTimerAdapter3
{4
public: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 second16
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

65
class SimpleTest : public SimpleTimerAdapter66
{67
public: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中写下测试代码:
int 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中写出了这样的代码:
我写出这段代码只是为了更清楚的显示代理类是如何工作的,除此之外,以上代码,没有任何作用,为真正的纯垃圾代码
为了抽象出一个中间代理类,我们需要用到模板,对于上面提到的定义问题,用模板可以很轻松的解决。同时,把最基本的内容从代理类中抽象出来。于是得以以下三个类:
/*2
* class Base,最终的功能类,目地的要跳转到Base的成员函数中去3
* class Impl,中间类,界于Adapt与Base之间4
* MemberCallBackType Base的成员函数5
* CallBackType,我们给API的回调函数6
*/7
template <class Base, class Impl, class MemberCallBackType, class CallBackType>8
class CallBackAdapter9
{10
protected: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 second19
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
}27
public: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

60
template <class Base>61
class TimerAdapter : public CallBackAdapter<62
Base,63
TimerAdapter<Base>,64
void (Base:: * )( HWND , UINT , UINT , DWORD ),65
void (CALLBACK *)( HWND , UINT , UINT , DWORD )>66
{67
public: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

测试代码如下:
int 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.codeproject.com/KB/atl/atl_underthehood_5.aspx
还有一篇也是CodeProject上的,但由于看文章的时间太久了,今天再去找时没有找到。



浙公网安备 33010602011771号