也说说Thunk

面向对象是个好东西,用接近世界的方式抽象程序世界,直观。

全局函数(或许我应该特指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 );


设为API:

 

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, 01000, a.MakeCallback(&SimpleTest::TimerProc2,&a)); 
 6
 7    MSG msg;
 8    while(!a.mQuit && GetMessage(&msg, 000) )
 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, 0100, 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, 000) )
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上的,但由于看文章的时间太久了,今天再去找时没有找到。

posted @ 2009-02-20 23:38  新起点_不能用  阅读(3506)  评论(1编辑  收藏  举报