由于最近項(xiàng)目是要開(kāi)發(fā)一個(gè)BHO瀏覽器插件,我們需要使用定時(shí)器來(lái)輪詢頁(yè)面的變化。實(shí)際上,就是讓定時(shí)器能夠訪問(wèn)成員變量,或者說(shuō)使定時(shí)器成為成員函數(shù)。但是,定時(shí)器調(diào)用的是一個(gè)回調(diào)函數(shù)(CALLBACK),回調(diào)函數(shù)是一個(gè)系統(tǒng)調(diào)用的函數(shù),它被封裝在類里面只能以static的方式定義。這種定義方式和我們的項(xiàng)目要求不符合,因?yàn)閟tatic函數(shù)只能訪問(wèn)static變量。
我們首先分析CALLBACK函數(shù)不能被封裝成成員變量的原因。一個(gè)對(duì)象的成員函數(shù)能夠訪問(wèn)類成員的關(guān)鍵是它先傳了對(duì)象的this指針給函數(shù),函數(shù)就能夠用this.xx的方法訪問(wèn)成員變量了。通過(guò)反匯編分析,我們發(fā)現(xiàn)其實(shí)就是給ECX寄存器傳了this指針過(guò)去,之后訪問(wèn)類成員時(shí),其地址就是ECX加偏移量,這就是對(duì)象的本質(zhì)。
為了達(dá)到這個(gè)目的,我嘗試了一些方法,均可行。但沒(méi)有Thunk來(lái)的直接。下面簡(jiǎn)單介紹一下:
1、全局變量法:通過(guò)全局map變量的方式記錄對(duì)象的指針,在設(shè)置定時(shí)器時(shí),將定時(shí)器的EventID和this指針作為鍵值對(duì)存到全局變量中。然后,定時(shí)器調(diào)用時(shí),EventID查詢當(dāng)前頁(yè)面的對(duì)象指針(完美解決)。
2、EventID:直接將對(duì)象的指針強(qiáng)轉(zhuǎn)成UINT類型當(dāng)成EventID傳入定時(shí)器設(shè)置的函數(shù),在定時(shí)器調(diào)用時(shí)再將EventID轉(zhuǎn)回this指針,這個(gè)方法應(yīng)該在64位操作系統(tǒng)中會(huì)失效(未驗(yàn)證)。
最終,我們選擇Thunk技術(shù)(微軟的ATL同樣適用Thunk技術(shù)封裝窗口)。Thunk技術(shù)的原理是使程序在運(yùn)行時(shí)直接執(zhí)行機(jī)器碼。在下面的代碼中將看到程序直接跳轉(zhuǎn)到分配的虛擬內(nèi)存上執(zhí)行代碼。
我們首先看代碼的核心部分:
- #pragma pack(push,1) //取消字節(jié)對(duì)齊開(kāi)始(編譯優(yōu)化指令)
- class Thunk
- {
- public:
- unsigned char m_mov; // "mov eax,"的字節(jié)碼
- unsigned int m_this; //this指針地址,結(jié)合上一句就是將this指針地址放入eax寄存器
- unsigned int m_xchg_push; //交換棧頂元素和EAX并入棧EAX
- unsigned char m_jmp; //跳轉(zhuǎn)指令字節(jié)碼 0XE9
- unsigned int m_relproc; //跳轉(zhuǎn)偏移量
- };
- #pragma pack(pop) //取消字節(jié)對(duì)齊結(jié)束
要讓程序執(zhí)行指定的機(jī)器碼之前,先要保證要執(zhí)行的機(jī)器碼正確,由于C++有內(nèi)存對(duì)齊的編譯優(yōu)化方法,先要告訴編譯器關(guān)閉此功能。上圖的字節(jié)碼實(shí)際構(gòu)成了一下內(nèi)容:
- mov eax, this
- xchg eax, [esp] : push eax
- jmp func
前兩句匯編指令執(zhí)行后,PC跳轉(zhuǎn)到func處時(shí),已經(jīng)有了this指針了,這時(shí)就能夠直接訪問(wèn)對(duì)象的成員變涼了。
下面直接上封裝后的全部代碼(VS2012編譯通過(guò))。
- /*
- ThunkTimer模板類封裝
- 作者:jedihy
- 時(shí)間:2013.11.23 2:09
- */
-
- #include <Windows.h>
- #include <iostream>
- #include <conio.h>
- #include <stdio.h>
-
- using namespace std;
-
- #pragma pack(push,1) //取消字節(jié)對(duì)齊開(kāi)始(編譯優(yōu)化指令)
- class Thunk
- {
- public:
- unsigned char m_mov; // "mov eax,"的字節(jié)碼
- unsigned int m_this; //this指針地址,結(jié)合上一句就是將this指針地址放入eax寄存器
- unsigned int m_xchg_push; //交換棧頂元素和EAX并入棧EAX
- unsigned char m_jmp; //跳轉(zhuǎn)指令字節(jié)碼 0XE9
- unsigned int m_relproc; //跳轉(zhuǎn)偏移量
- };
- #pragma pack(pop) //取消字節(jié)對(duì)齊結(jié)束
-
- 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;//對(duì)成員變量的訪問(wèn)
-
- }
-
- void ThunkTimer::Init(){
-
- typedef void (_stdcall ThunkTimer::*TMFP)();
- //用union的特點(diǎn)巧取成員地址
- 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的字節(jié)碼就是0xe9
- thunk->m_mov = 0xB8; //mov eax, 的字節(jié)碼
- thunk->m_this = (unsigned int)(void*)this; //this指針
- thunk->m_xchg_push = 0x50240487; //交換
- thunk->m_relproc = addr.func - (unsigned int)(void *)(thunk +1); //計(jì)算偏移量
- }
-
- int main()
- {
- ThunkTimer timer;
- timer.Init();
- timer.setTimer(100);
-
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0)) {
- if (_kbhit()) {
- break;
- }
- DispatchMessage(&msg);
- }
- return 0;
- }
|