Thunk技术的一个改进
2010-08-15 20:46:25 来源:WEB开发网原 理部分到此为止。下面举一个完整的,有实际意义的例子。在windows中,回调函 数的使用是很常见的。比如窗口过程,又比如定时器回调函数。这些函数,你写 好代码,但是却从不直接调用。相反,你把函数地址传递给系统,当系统检测到 某些事件发生的时候,系统来调用这些函数。这样当然很好,不过如果你想做一 个封装,将所有相关部分写成一个类,那问题就来了。
问题是,这些回调 函数的形式事先已经定义好了,你无法让一个类的成员函数成为一个回调函数, 因为类型不可能匹配。这不能怪微软,微软不可能将回调函数定义为一个类成员 函数(该定义为什么类?),而只能将回调函数定义为一个全局的函数。并且微 软其实很多时候也提供了补救措施,在回调函数中增加了一个void *的参数。这 个参数一般都用来传递类的this指针。这样一来,可以这样解决:给系统提供一 个全局函数作为回调函数,在该函数中通过额外的那个void *参数访问到类的对 象,从而直接调用到类成员函数。如此,你的封装一样可以完成,不过多了一次 函数调用而已。
但是,不是所有的回调函数都这么幸运,微软都给它们提 供了一个额外的参数。比如,定时器的回调函数就没有。
VOID CALLBACK TimerProc(
HWND hwnd, // handle to window
UINT uMsg, // WM_TIMER message
UINT_PTR idEvent, // timer identifier
DWORD dwTime // current system time
);
四个参数,个个都有用途。没有地方可以让你传递那个this指针 。当然了,你实在要传也可以做到,比如将hwnd设置为一个结构体的指针,其中 包含原来的hwnd和一个this指针。在定时器回调函数中取出hwnd后强制转化为结 构体指针,取出原来的hwnd,取出this指针。现在就可以通过this指针自由的调 用类成员函数了。不过这种方法不是我想要的,我要的是一个通用,统一的解决 方法。通过在参数里面加塞夹带的方法,一般也是没有问题的,不过如果碰到一 个回调函数没有参数怎么办?另外,本来是封装为一个类的,结果还是要带着一 个全局函数,你难道不觉得有些不爽吗?
这正是thunk技术大显身手的地 方了。我们知道,所谓类成员函数,和对应的全局函数,其实就差一个this指针 。如果我们在系统调用函数之前正确处理好this指针,那系统就可以正确的调用 类成员函数。
具体的思路是这样的:当系统需要一个回调函数地址的时候 ,我们传递一个thunk代码段的地址。这个代码段做两件事:
1、准备好 this指针
2、调用成员函数
关键的代码如下(完整的工程在附件中 ):
void ThunkTemplate(DWORD& addr1,DWORD& addr2,int calltype=0)
{
int flag = 0;
DWORD x1,x2;
if(flag)
{
__asm //__thiscall
{
thiscall_1: mov ecx,-1; //-1占位符,运行时将 被替换为this指针.
mov eax,-2; //-2占位符,运行时将 被替换为CTimer::CallBcak的地址.
jmp eax;
thiscall_2: ;
}
__asm //__stdcall
{
stdcall_1: push dword ptr [esp] ; //保存(复制 )返回地址到当前栈中
mov dword ptr [esp+4], -1 ; // 将this指针送入栈中,即原来的返回地址处
mov eax, - 2;
jmp eax ; //跳转至目标消息处理 函数(类成员函数)
stdcall_2: ;
}
}
if(calltype==0)//this_call
{
__asm
{
mov x1,offset thiscall_1; //取 Thunk代码段 的地 址范围.
mov x2,offset thiscall_2 ;
}
}
else
{
__asm
{
mov x1,offset stdcall_1;
mov x2,offset stdcall_2 ;
}
}
addr1 = x1;
addr2 = x2;
}
更多精彩
赞助商链接