如何让API回调你的VC类成员函数而不是静态函数
首先需要包含一個(gè)由yzwykkldczsh同志編寫(xiě)的模板類-----萬(wàn)能多用自適應(yīng)無(wú)限制回調(diào)模板(為紀(jì)念友人fishskin,此模板又稱為H>W模板)?
/**************************************************************************
?*?? ACCallback.h
?*?? Helper class of Member function callback mechanism
?**************************************************************************/
#include "stdafx.h"
#include "windows.h"
#pragma pack(push, 1)
struct _ACCallbackOpCodes
{
?unsigned char tag;??// CALL e8
?LONG_PTR offset;??// offset (dest - src - 5, 5=sizeof(tag + offset))
?LONG_PTR _this;???// a this pointer
?LONG_PTR _func;???// pointer to real member function address
};
#pragma pack(pop)
static __declspec( naked ) int STDACJMPProc()
{
?_asm
?{
??POP ECX?
??MOV EAX, DWORD PTR [ECX + 4]?// func
??MOV ECX, [ECX]?????// this??
??JMP EAX
?}
}
static LONG_PTR CalcJmpOffset(LONG_PTR Src, LONG_PTR Dest)
{
?return Dest - (Src + 5);
}
/*
?* NOTE: _TPStdFunc: a type of function pointer to API or Callbacks, *MUST* be _stdcall
???????? _TPMemberFunc: a type of function pointer to class member function,
???????? *MUST* be the *DEFAULT* calling conversation, *NO* prefix should be added,
????????? that is, using ECX for "this" pointer, pushing parameters from right to left,
????????? and the callee cleans the stack.
????????? _TClass: the class who owns the callback function. The caller should only own the _stdcall function pointer
?? LIFE TIME:? It is important to keep the ACCallback object alive until the CALLBACK is not required!!!
?*/
template<typename _TPStdFunc, class _TClass, typename _TPMemberFunc>
class ACCallback
{
public:
?_TClass *m_pThis;
?_TPMemberFunc m_pFunc;
private:
?_TPStdFunc m_pStdFunc;
?void MakeCode()
?{
??if (m_pStdFunc) ::VirtualFree(m_pStdFunc, 0, MEM_RELEASE);
??m_pStdFunc = (_TPStdFunc)::VirtualAlloc(NULL, sizeof(_ACCallbackOpCodes), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
??_ACCallbackOpCodes *p = (_ACCallbackOpCodes *)m_pStdFunc;
??p->_func = *(LONG_PTR *)&m_pFunc;
??p->_this = (LONG_PTR)m_pThis;
??p->tag = 0xE8;
??p->offset = CalcJmpOffset((LONG_PTR)p, (LONG_PTR)STDACJMPProc);
?}
public:
?ACCallback<_TPStdFunc, _TClass, _TPMemberFunc>()
?{
?}
?ACCallback<_TPStdFunc, _TClass, _TPMemberFunc>(_TClass* pThis,
??_TPMemberFunc pFunc
??)
?{
??m_pFunc = pFunc;
??m_pThis = pThis;
??m_pStdFunc = NULL;
??MakeCode();
?}
?void Assign(_TClass* pThis,
??_TPMemberFunc pFunc
??)
?{
??m_pFunc = pFunc;
??m_pThis = pThis;
??m_pStdFunc = NULL;
??MakeCode();
?}
?~ACCallback<_TPStdFunc, _TClass, _TPMemberFunc>()
?{
??::VirtualFree(m_pStdFunc, 0, MEM_RELEASE);
?}
?operator _TPStdFunc()
?{
??return m_pStdFunc;
?}
};
/********************************** EXAMPLE **********************************
class CClass1
{
public:
?TCHAR m_Buf[255];
?BOOL EnumWindowProc(HWND hwnd, LPARAM lp)
?{
??GetWindowText(hwnd, m_Buf, 255);
??printf("Enum window=%s/n", m_Buf);
??return TRUE;
?}
?typedef BOOL (CClass1::*CLASSWNDENUMPROC)(HWND, LPARAM);
};
TO USE:
?CClass1 c1;
?ACCallback<WNDENUMPROC, CClass1, CClass1::CLASSWNDENUMPROC> cb(&c1, &CClass1::EnumWindowProc);
?EnumWindows(cb, 0);
************************* END OF EXAMPLE *********************************/
模板的三個(gè)參數(shù)分別是:API函數(shù)指針的類型,類名字,類成員函數(shù)指針的類型(兩種函數(shù)指針在參數(shù)和返回值上應(yīng)該一樣,只是前者聲明為_(kāi)stdcall,后者不加任何調(diào)用修飾,即默認(rèn)的__thiscall方式)
該項(xiàng)頭文件的注釋中給了一個(gè)調(diào)用API函數(shù)EnumWindows的例子。現(xiàn)在偶們來(lái)試試調(diào)用SetTimer。
class CTestCallback
{
private:
?/* A callback of SetTimer, mirrored into member OnTimer */
?typedef void (CTestCallback::*CLASSTIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
?void OnTimer (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
?ACCallback<TIMERPROC, CTestCallback, CLASSTIMERPROC> m_DOnTimer;
}
調(diào)用時(shí),只要這樣寫(xiě):
/*?初始化回調(diào)結(jié)構(gòu) */
m_DOnTimer.Assign(this, &CTestCallback::OnTimer);
m_uid = ::SetTimer( NULL, 0, 1000, m_DOnTimer);
最后記得在CTestCallback的析構(gòu)函數(shù)中KillTimer。由于m_DOnTimer會(huì)實(shí)現(xiàn)轉(zhuǎn)化到靜態(tài)函數(shù)指針類型的操作符,所以調(diào)用的地方只要直接寫(xiě)回調(diào)結(jié)構(gòu)的名字就可以了。
使用該模板需要注意兩點(diǎn):
1.API函數(shù)應(yīng)當(dāng)是_stdcall類型的(這一點(diǎn)絕大部分API都滿足)。類成員函數(shù)必須是默認(rèn)的調(diào)用方式,不要加_stdcall或_cdecl之類的修飾。此方式的重要條件就在于_stdcall和__thiscall之間只相差了一個(gè)ECX指出的this指針,所以我們才能實(shí)現(xiàn)這種映射(這種方式在VCL和ATL的窗口類中都有使用到);
2.回調(diào)結(jié)構(gòu)的生存周期應(yīng)當(dāng)是在整個(gè)回調(diào)函數(shù)有效的時(shí)間內(nèi)。因此,對(duì)于EnumWindows這樣的函數(shù),只要聲明在棧上就可以了;但對(duì)于SetTimer,就必須定義為類成員變量,同時(shí),在類的析構(gòu)函數(shù)中必須及時(shí)銷毀這個(gè)timer。
?
CLASSTIMERPROC是自定義類型,它的類型是
typedef void (CTestCallback::*CLASSTIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
除了是類成員函數(shù)外,其聲明與API需要的回調(diào)函數(shù)TIMERPROC類型相同。在需要顯式傳遞TIMERPROC的地方,不需要做轉(zhuǎn)換,因?yàn)锳CCallback重載了操作符operator _TPStdFunc()
setwindows(hWnd,gwl_wndproc,m_Dmyproc) ,(應(yīng)該是SetWindowLong(hWnd, GWL_WNDPROC, m_Dmyproc)吧?) 這里需要的參數(shù)是LONG,并不是偶們的函數(shù)提供的重載操作符的類型WNDPROC。只寫(xiě)LONG的話是轉(zhuǎn)不過(guò)去的,因?yàn)轭惓蓡T函數(shù)不能轉(zhuǎn)成一個(gè)指針,因此這里需要轉(zhuǎn)換兩次。寫(xiě)法是:
SetWindowLong(hWnd, GWL_WNDPROC, (LONG)(WNDPROC)m_Dmyproc);
========================================================
最近在壇子上看到很多有關(guān)這方面的討論的文章,看來(lái)確實(shí)有再解釋一下的必要了。
1.它能實(shí)現(xiàn)什么?
簡(jiǎn)而言之,它能把一個(gè)普通的類成員函數(shù)變成為一個(gè)stdcall的靜態(tài)函數(shù)。就是說(shuō)從沒(méi)有this指針到獲得一個(gè)this指針。
它的用途,發(fā)散開(kāi)來(lái),可以用于:->窗口過(guò)程 ->線程過(guò)程 ->HOOK函數(shù)(特別是多線程下的HOOK函數(shù)) 可以使原來(lái)打亂程序面向?qū)ο蟮牡胤阶兊梅奖恪T贒elphi和C#中,類成員函數(shù)的委托調(diào)用使我們的程序變得非常簡(jiǎn)潔,如果在下一代C++環(huán)境中也使用這種方法,想必會(huì)為VC程序員提供不少便利。
聽(tīng)起來(lái)有點(diǎn)不可思議,但想明白了就很簡(jiǎn)單。舉個(gè)例子:有一個(gè)CMyTimer類,它要調(diào)用SetTimer方法。它需要SetTimer回調(diào)它的成員函數(shù)CMyTimer::OnTimer。但是,在內(nèi)存中,每個(gè)CMyTimer的實(shí)例是共享同一個(gè)OnTimer函數(shù)段的,在調(diào)用之前,調(diào)用方負(fù)責(zé)把this指針?lè)旁贓CX里面?zhèn)鹘o它。在不修改回調(diào)方的這種情況下似乎沒(méi)辦法取得this指針。但我們改變一個(gè)思路,我們不傳OnTimer的地址給回調(diào)方,而是為每個(gè)CMyTimer實(shí)例分配一段新的函數(shù)空間,這樣在回調(diào)時(shí)就能根據(jù)當(dāng)前地址查找到自己是哪一個(gè)實(shí)例了。更簡(jiǎn)單的,我們干脆就在這段函數(shù)空間中嵌入幾個(gè)指令,讓它自動(dòng)把this指針取到放在ECX里面。這就是本方法的原理。
2.這種技術(shù)是什么?
這種技術(shù)稱之為THUNK技術(shù)。(本文貼出來(lái)后,聽(tīng)別人說(shuō)才知道的-___-b,我也是在VCL源代碼中找到,然后改進(jìn)到我的VC程序中)。它的關(guān)鍵在于取得當(dāng)前地址。用MOV ECX, EIP顯然是不可能的。于是我們用CALL指令。CALL指令會(huì)把下一條指令地址放到堆棧中。再POP它出來(lái),就拿到了此地址。然后訪問(wèn)它可以得到先前放入的this指針和函數(shù)指針。
3.要注意什么?
要注意的方面很多,一個(gè)是內(nèi)存的問(wèn)題,這段內(nèi)存必須是有執(zhí)行的權(quán)限,并且生存周期要足夠長(zhǎng),直到回調(diào)不再需要。還有就是調(diào)用約定的問(wèn)題,使用本模板可以處理任何類型的函數(shù),但必須是普通類成員函數(shù)轉(zhuǎn)為stdcall的約定。如果不滿足此條件,則需要自己動(dòng)手寫(xiě)模板。
?
轉(zhuǎn)自:http://peirenlei.javaeye.com/blog/305064
總結(jié)
以上是生活随笔為你收集整理的如何让API回调你的VC类成员函数而不是静态函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: grub2的配置文件grub.cfg详解
- 下一篇: 用 VC++ 2008 编写 Windo