打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
也说说Thunk - 桃之夭夭,灼灼其华.

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

全局函数(或许我应该特指Windows API)也是好东西,要什么调什么,毫不含糊.

那么,当他们走到一起,矛盾就产生

类时刻保护着自己的成员,以至于为每一个方法加入一个指向自己的指针.

比如有以下类

 

1
class TestClass()
2
{
3
    
void Func();
4
}
;

 

则Func被编译器安插了this以针,以便Func内部可以访问类TestClass的成员变量,即Func变为如下样子

 

1
void Func(TestClass* this);

 

在实际的开发中,使用API时常常会要求我们提供回调函数,比如SetTimer,我们需要设置向这个API提供一个如下类型的函数指针:

 

1
typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);

 

假如我们有如下类

1
class TestClass()
2
{
3
    
void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime )
4
    
{
5
        
//do something
6
    }

7
}

8

9

 

 

并希望将成员函数

 

1
void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );


设为API:

 

1
UINT_PTR
2
WINAPI
3
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指针,变成如下形式:

 

1
void OnTimeProc(TestClass *this, HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );

 

很显然,我们无法直接设置。

那我们应该怎么做呢,在完成这个任务之前,让我们先看一下一个稍简单一点的例子,用以说明Thunk原理。

Thunk的原理其实说起来很简单:巧妙的将数据段的几个字节的数据设为特殊的值,然后告诉系统,这几个字节的数据是代码(即将一个函数指针指向这几个字节的第一个字节),让系统来执行。

这样说起来就很简单.

相信对于后一个操作:将一个函数指针指向这几个字节的第一个字节我们都应该会:

比如有结构体:

 

1
typedef struct thunk
2
{
3
    DWORD    dwMovEsp;
4
    DWORD    dwThis;
5
    BYTE    bJmp;
6
    DWORD    dwRealProc; 
7

8
}
THUNK; 
9

 

函数指针:

 

1
typedef void (*FUNC)(DWORD dwThis); 

 

则如下代码将一个thunk的结构体强转为FUNC型的函数指针:

 

1
THUNK testThunk; 
2

3
FUNC fun 
= (FUNC)&testThunk; 
4

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

 

这样,系统便会把testThunk所指向的内存加载到缓冲中。

现在的总是是将这个结构体设为多少比较好?

在x86 指令集中,我们可以查到:

汇编指令JMP为0xe9

所以,我们写下如下函数用于设置这个结构体的值:

 

1
void 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回调时,他应该调用哪一个类的成员方法,类的面员方法的函数指针时需要指定类类型。如下所示:

 

1
void (Base:: *  )( HWND , UINT , UINT , DWORD ); 

 

看到这里,相信任何一个初级的刚入门的c++程序员都可以快速的写下以下类:

 

 1
class SimpleTest;
 2
class SimpleTimerAdapter
 3
{
 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 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

65
class SimpleTest : public SimpleTimerAdapter
66
{
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中写下测试代码:

 

 1
int 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
*/

 7
template 
<class Base, class Impl, class MemberCallBackType, class CallBackType>
 8
class CallBackAdapter
 9
{
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 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
    }

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

测试代码如下:

 

 1
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, 
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上的,但由于看文章的时间太久了,今天再去找时没有找到。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
AutoIT中多定时器用法实例
VC++实现非窗口类中使用定时器的方法
非窗口类中使用定时器的方法
Win32控制台程序的定时器实现
基于Thunk技术的Windows Timer的封装
Windows API教程(一) Window API 概要
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服