分享

基于Thunk技术的Windows Timer的封装

 SamBookshelf 2013-12-24

        由于最近项目是要开发一个BHO浏览器插件,我们需要使用定时器来轮询页面的变化。实际上,就是让定时器能够访问成员变量,或者说使定时器成为成员函数。但是,定时器调用的是一个回调函数(CALLBACK),回调函数是一个系统调用的函数,它被封装在类里面只能以static的方式定义。这种定义方式和我们的项目要求不符合,因为static函数只能访问static变量。

    我们首先分析CALLBACK函数不能被封装成成员变量的原因。一个对象的成员函数能够访问类成员的关键是它先传了对象的this指针给函数,函数就能够用this.xx的方法访问成员变量了。通过反汇编分析,我们发现其实就是给ECX寄存器传了this指针过去,之后访问类成员时,其地址就是ECX加偏移量,这就是对象的本质。

        为了达到这个目的,我尝试了一些方法,均可行。但没有Thunk来的直接。下面简单介绍一下:

1、全局变量法:通过全局map变量的方式记录对象的指针,在设置定时器时,将定时器的EventID和this指针作为键值对存到全局变量中。然后,定时器调用时,EventID查询当前页面的对象指针(完美解决)。

2、EventID:直接将对象的指针强转成UINT类型当成EventID传入定时器设置的函数,在定时器调用时再将EventID转回this指针,这个方法应该在64位操作系统中会失效(未验证)。

最终,我们选择Thunk技术(微软的ATL同样适用Thunk技术封装窗口)。Thunk技术的原理是使程序在运行时直接执行机器码。在下面的代码中将看到程序直接跳转到分配的虚拟内存上执行代码。

我们首先看代码的核心部分:

  1. #pragma pack(push,1)    //取消字节对齐开始(编译优化指令)  
  2. class Thunk  
  3. {  
  4. public:  
  5.     unsigned char    m_mov; // "mov eax,"的字节码  
  6.     unsigned int    m_this; //this指针地址,结合上一句就是将this指针地址放入eax寄存器  
  7.     unsigned int    m_xchg_push; //交换栈顶元素和EAX并入栈EAX  
  8.     unsigned char   m_jmp;  //跳转指令字节码 0XE9  
  9.     unsigned int    m_relproc;  //跳转偏移量  
  10. };  
  11. #pragma pack(pop) //取消字节对齐结束  
要让程序执行指定的机器码之前,先要保证要执行的机器码正确,由于C++有内存对齐的编译优化方法,先要告诉编译器关闭此功能。
  1. #pragma pack(push,1)  
上图的字节码实际构成了一下内容:

  1. mov eax, this  
  2. xchg eax, [esp] : push eax  
  3. jmp func  
前两句汇编指令执行后,PC跳转到func处时,已经有了this指针了,这时就能够直接访问对象的成员变凉了。

下面直接上封装后的全部代码(VS2012编译通过)。

  1. /* 
  2. ThunkTimer模板类封装 
  3. 作者:jedihy 
  4. 时间:2013.11.23 2:09 
  5. */  
  6.   
  7. #include <Windows.h>  
  8. #include <iostream>  
  9. #include <conio.h>  
  10. #include <stdio.h>  
  11.   
  12. using namespace std;  
  13.   
  14. #pragma pack(push,1)    //取消字节对齐开始(编译优化指令)  
  15. class Thunk  
  16. {  
  17. public:  
  18.     unsigned char    m_mov; // "mov eax,"的字节码  
  19.     unsigned int    m_this; //this指针地址,结合上一句就是将this指针地址放入eax寄存器  
  20.     unsigned int    m_xchg_push; //交换栈顶元素和EAX并入栈EAX  
  21.     unsigned char   m_jmp;  //跳转指令字节码 0XE9  
  22.     unsigned int    m_relproc;  //跳转偏移量  
  23. };  
  24. #pragma pack(pop) //取消字节对齐结束  
  25.   
  26. class ThunkTimer{  
  27. public:  
  28.     ThunkTimer(){  
  29.         objid = (int)this;  
  30.         cout<<objid<<endl;  
  31.     }  
  32.     int objid;  
  33.     Thunk* thunk;  
  34.     void Init();  
  35.     void setTimer(unsigned int);  
  36.     void CALLBACK nativetimer(HWND, UINT, UINT, DWORD);  
  37. };  
  38. void ThunkTimer::setTimer(unsigned int timeout_ms){  
  39.     ::SetTimer(0,0,timeout_ms,(TIMERPROC )thunk);  
  40. }  
  41. void CALLBACK ThunkTimer::nativetimer(HWND hWnd, UINT uMsg, UINT uEvent, DWORD dwTime){  
  42.     cout<<objid<<endl;//对成员变量的访问  
  43.   
  44. }  
  45.   
  46. void ThunkTimer::Init(){  
  47.   
  48.     typedef void (_stdcall ThunkTimer::*TMFP)();  
  49.     //用union的特点巧取成员地址  
  50.     union {  
  51.         unsigned int func;  
  52.         TMFP method;  
  53.     } addr;  
  54.     addr.method =(TMFP)&ThunkTimer::nativetimer;  
  55.     thunk = (Thunk*)VirtualAlloc(NULL, sizeof(Thunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);  
  56.     thunk->m_jmp       = 0xE9;   //JMP的字节码就是0xe9  
  57.     thunk->m_mov       = 0xB8;   //mov eax, 的字节码  
  58.     thunk->m_this       = (unsigned int)(void*)this; //this指针  
  59.     thunk->m_xchg_push    = 0x50240487;  //交换  
  60.     thunk->m_relproc    = addr.func - (unsigned int)(void *)(thunk +1);  //计算偏移量  
  61. }  
  62.   
  63. int main()  
  64. {  
  65.     ThunkTimer timer;  
  66.     timer.Init();  
  67.     timer.setTimer(100);  
  68.   
  69.     MSG msg;  
  70.     while (GetMessage(&msg, NULL, 0, 0)) {  
  71.         if (_kbhit()) {  
  72.             break;  
  73.         }  
  74.         DispatchMessage(&msg);  
  75.     }  
  76.     return 0;  
  77. }  


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多