由于最近项目是要开发一个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技术的原理是使程序在运行时直接执行机器码。在下面的代码中将看到程序直接跳转到分配的虚拟内存上执行代码。
我们首先看代码的核心部分:
- #pragma pack(push,1) //取消字节对齐开始(编译优化指令)
- class Thunk
- {
- public:
- unsigned char m_mov; // "mov eax,"的字节码
- unsigned int m_this; //this指针地址,结合上一句就是将this指针地址放入eax寄存器
- unsigned int m_xchg_push; //交换栈顶元素和EAX并入栈EAX
- unsigned char m_jmp; //跳转指令字节码 0XE9
- unsigned int m_relproc; //跳转偏移量
- };
- #pragma pack(pop) //取消字节对齐结束
要让程序执行指定的机器码之前,先要保证要执行的机器码正确,由于C++有内存对齐的编译优化方法,先要告诉编译器关闭此功能。上图的字节码实际构成了一下内容:
- mov eax, this
- xchg eax, [esp] : push eax
- jmp func
前两句汇编指令执行后,PC跳转到func处时,已经有了this指针了,这时就能够直接访问对象的成员变凉了。
下面直接上封装后的全部代码(VS2012编译通过)。
- /*
- ThunkTimer模板类封装
- 作者:jedihy
- 时间:2013.11.23 2:09
- */
-
- #include <Windows.h>
- #include <iostream>
- #include <conio.h>
- #include <stdio.h>
-
- using namespace std;
-
- #pragma pack(push,1) //取消字节对齐开始(编译优化指令)
- class Thunk
- {
- public:
- unsigned char m_mov; // "mov eax,"的字节码
- unsigned int m_this; //this指针地址,结合上一句就是将this指针地址放入eax寄存器
- unsigned int m_xchg_push; //交换栈顶元素和EAX并入栈EAX
- unsigned char m_jmp; //跳转指令字节码 0XE9
- unsigned int m_relproc; //跳转偏移量
- };
- #pragma pack(pop) //取消字节对齐结束
-
- class ThunkTimer{
- public:
- ThunkTimer(){
- objid = (int)this;
- cout<<objid<<endl;
- }
- int objid;
- Thunk* thunk;
- void Init();
- void setTimer(unsigned int);
- void CALLBACK nativetimer(HWND, UINT, UINT, DWORD);
- };
- void ThunkTimer::setTimer(unsigned int timeout_ms){
- ::SetTimer(0,0,timeout_ms,(TIMERPROC )thunk);
- }
- void CALLBACK ThunkTimer::nativetimer(HWND hWnd, UINT uMsg, UINT uEvent, DWORD dwTime){
- cout<<objid<<endl;//对成员变量的访问
-
- }
-
- void ThunkTimer::Init(){
-
- typedef void (_stdcall ThunkTimer::*TMFP)();
- //用union的特点巧取成员地址
- union {
- unsigned int func;
- TMFP method;
- } addr;
- addr.method =(TMFP)&ThunkTimer::nativetimer;
- thunk = (Thunk*)VirtualAlloc(NULL, sizeof(Thunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- thunk->m_jmp = 0xE9; //JMP的字节码就是0xe9
- thunk->m_mov = 0xB8; //mov eax, 的字节码
- thunk->m_this = (unsigned int)(void*)this; //this指针
- thunk->m_xchg_push = 0x50240487; //交换
- thunk->m_relproc = addr.func - (unsigned int)(void *)(thunk +1); //计算偏移量
- }
-
- int main()
- {
- ThunkTimer timer;
- timer.Init();
- timer.setTimer(100);
-
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0)) {
- if (_kbhit()) {
- break;
- }
- DispatchMessage(&msg);
- }
- return 0;
- }
|