WEB开发网
开发学院软件开发VC Thunk 技术的一个改进 阅读

Thunk 技术的一个改进

 2008-03-27 21:40:28 来源:WEB开发网   
核心提示: 但是,不是所有的回调函数都这么幸运,Thunk 技术的一个改进(4),微软都给它们提供了一个额外的参数,比如,调用这两个函数生成机器码应该比手工查找方便多了,如果你也这样认为,定时器的回调函数就没有,VOID CALLBACK TimerProc(HWND hwnd, // handle

但是,不是所有的回调函数都这么幸运,微软都给它们提供了一个额外的参数。比如,定时器的回调函数就没有。

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;
}

上面的函数有几个地方需要说明:

1、为了能适应两种不同的成员函数调用约定,这里写了两份代码。通过参数calltype决定拷贝哪一份代码到缓冲区。

2、本来一条jmp xxxx;指令这里分解为两条指令:

mov eax,-2;
jmp eax;

这是由汇编语言的特点决定的。直接写jmp -2是通不过的(根据地址的不同,jmp汇编后可能出现好几种形式。这里必须出现一个真实的地址以便汇编器决定jmp类型)。

3、如果对this指针的知识不清楚,请参考我在vc知识库的另外一篇文章《直接调用类成员函数地址》。

设置thunk代码的完整代码如下:

  DWORD FuncAddr;
  GetMemberFuncAddr_VC6(FuncAddr,&CTimer::CallBcak);
  DWORD addr1,addr2;
  ThunkTemplate(addr1,addr2,0);
  memset(m_thunk,0,100);
  memcpy(m_thunk,(void*)addr1,addr2-addr1);
  ReplaceCodeBuf(m_thunk,addr2-addr1,-1,(DWORD)((void*)this)); //将-1替换为this指针.
    ReplaceCodeBuf(m_thunk,addr2-addr1,-2,FuncAddr); //将-2替换为成员函数的指针.

如果你还想和以前一样直接在数组中赋值机器码(毕竟这样看起来很酷,我完全理解)。那也可以这样,调用ThunkTemplate生成m_thunk后,打印出该数组的值,而后在程序中直接给m_thunk数组赋值,就象网上大部分thunk代码那样,当然在调用前要多一个步骤就是替换掉占位数。不过无论如何,调用这两个函数生成机器码应该比手工查找方便多了,如果你也这样认为,那就算我这篇文章没白写。

上一页  1 2 3 4 

Tags:Thunk 技术 一个

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接