2013-10-28 56 views
1

你們很多人都熟悉ATL thunk例如用於窗口創建。使此工作成功的類CStdCallThunk正在調用WindowProc。在本質上它將全局回調轉換爲C++對象的成員函數。簡單的窗口回調thunks for x64

這種類型的thunk不適用於需要第一個參數不變的SetWindowsHookEx callback。對於32位窗口,我在庫的CAuxThunk中找到了一個整潔的解決方案。不幸的是,這是不工作的原生64位可執行文件

我想知道是否有任何x64程序集大師可以修補此CAuxThunk工作64位窗口或想出任何相當的thunk,將此__stdcall回調變成一個成員函數?

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) 

感謝,
尼科斯

+0

這聽起來很不對頭,你應該只對不屬於你的窗口使用鉤子。 –

回答

0

因爲在64位模式的低級別的約定對所有類型(STDCALL,CDECL和thiscall)你並不真正需要的彙編代碼來實現這一相同。只需創建一個調用相應成員函數的靜態函數即可。您需要找出適當的this指針,例如通過將lparam中的hwnd與對象相關聯。如果你只有一個回調,這當然不成問題。喜歡的東西:

LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) 
{ 
    Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd); 
    return w->CallWndProc(nCode, wParam, lParam); 
} 
+0

這樣一個假設的GetWindowByHwnd將需要某種單例或其他方法來查找C++類的窗口句柄。我試圖避免所有這些,因此需要一個優雅的低級別thunk – nikos

+0

我不明白如果你有多個thunk,那麼低級別的thunk會找出哪個實例要調用。 – Jester

+1

你可以使用'GetWindowLongPtr'來獲得這個指針。我記得雖然看到了x64的thunk解決方案,但我仍會嘗試再次找到該解決方案。 –

0

更新見下

看看SetWindowLongPtr http://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspx

這可以讓你的指針與窗口關聯。鑑於WindowProc中看起來像這樣

LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam );

可以使用HWND參數與GetWindowLongPtr檢索this指針在回調。

更新

看看How to thunk a function in x86 and x64? (Like std::bind in C++, but dynamic)

這似乎是你在找什麼

+0

正如我在我的開場白中所說的那樣,問題不在於窗口過程,而在於鉤住。 ATL thunking很好地處理窗口過程。我需要的是非窗口類的東西。我沒有一個與之相關的窗口,這是無關緊要的 – nikos

+0

@nikos你要安裝多少個thunk?這是一個有限的數字,或者可以是任何東西? –

+0

感謝您的更新後的帖子和鏈接,這看起來很有前途,但我必須先測試它(我沒有弄清代碼的作用,讓我們看看它是否工作:)。我不使用很多這些鉤子,通常當我用窗口做某事時,我可能會實例化一個臨時鉤子(包裝在一個整潔的C++對象中),完成工作然後卸載鉤子(銷燬對象) – nikos

3

我描述了一種通用的方式產生this answer想要的任何的thunk代碼。讓我們爲你的情況重做它,只是爲了好玩。

假設爲你的類定義:

struct YourClass { 
    LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam); 
}; 

基本上你寫你的thunk在C++中有佔位符的實際地址:

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) { 
    YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122); 
    __int64 im = 0x3344334433443344; 
    LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im); 
    return (x->*m)(nCode, wParam, lParam); 
} 

,並調用它的方式,以防止編譯以內聯該呼叫:

int main() { 
    LRESULT (CALLBACK *volatile fp)(int, WPARAM, LPARAM) = CallWndProc; 
    fp(0, 0, 0); 
} 

在釋放中編譯並查看生成的程序集(在Visual Studio中,看到調試過程中的裝配窗口,打開「顯示代碼字節」):

4D 8B C8      mov   r9,r8 
4C 8B C2      mov   r8,rdx 
8B D1       mov   edx,ecx 
48 B9 22 11 22 11 22 11 22 11 mov   rcx,1122112211221122h 
48 B8 44 33 44 33 44 33 44 33 mov   rax,3344334433443344h 
48 FF E0      jmp   rax 

這將是您的thunk,與44 33 44 33 44 33 44 33與指針替換爲您的會員(&YourClass::YourMemberFunc)和22 11 22 11 22 11 22 11在運行時用指向實際對象實例的指針替換。

發生了什麼事情在thunk的說明:

在64位調用約定(其中只有一個Windows下),前四個參數傳遞在rcx, rdx, r8, r9寄存器,在這個秩序,從左到右。所以,當我們的thunk被調用,我們有

rcx = nCode, rdx = wParam, r8 = lParam 

對於成員函數有控股this指針隱式的第一個參數,所以在進入YourMemberFunc時,我們必須有

rcx = this, rdx = nCode, r8 = wParam, r9 = lParam 

編譯器生成的代碼正是這樣做的調整:它轉移r8 -> r9, rdx -> r8, ecx -> edx,然後將我們的佔位符this = 1122112211221122指定爲rcx。現在它已經設置了參數,它可以繼續間接跳轉到函數本身。 rax用於保存返回值,因此它不必在函數調用中保留。這就是爲什麼它在這裏用來臨時保存目標地址的原因,這爲尾部呼叫優化提供了機會(一次呼叫/返回對由單次跳轉代替)。

爲什麼我們必須做間接調用?否則我們會得到一個相對跳躍。但是我們不能使用硬編碼的相對跳轉,因爲thunk將被複制到內存中的不同地址!因此,我們在運行時設置絕對地址,並進行間接跳轉。

HTH