如何让API回调你的VC类成员函数而不是静态函数
首先需要包含一個由yzwykkldczsh同志編寫的模板類-----萬能多用自適應無限制回調模板(為紀念友人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 *********************************/
模板的三個參數分別是:API函數指針的類型,類名字,類成員函數指針的類型(兩種函數指針在參數和返回值上應該一樣,只是前者聲明為_stdcall,后者不加任何調用修飾,即默認的__thiscall方式)
該項頭文件的注釋中給了一個調用API函數EnumWindows的例子。現在偶們來試試調用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;
}
調用時,只要這樣寫:
/*?初始化回調結構 */
m_DOnTimer.Assign(this, &CTestCallback::OnTimer);
m_uid = ::SetTimer( NULL, 0, 1000, m_DOnTimer);
最后記得在CTestCallback的析構函數中KillTimer。由于m_DOnTimer會實現轉化到靜態函數指針類型的操作符,所以調用的地方只要直接寫回調結構的名字就可以了。
使用該模板需要注意兩點:
1.API函數應當是_stdcall類型的(這一點絕大部分API都滿足)。類成員函數必須是默認的調用方式,不要加_stdcall或_cdecl之類的修飾。此方式的重要條件就在于_stdcall和__thiscall之間只相差了一個ECX指出的this指針,所以我們才能實現這種映射(這種方式在VCL和ATL的窗口類中都有使用到);
2.回調結構的生存周期應當是在整個回調函數有效的時間內。因此,對于EnumWindows這樣的函數,只要聲明在棧上就可以了;但對于SetTimer,就必須定義為類成員變量,同時,在類的析構函數中必須及時銷毀這個timer。
?
CLASSTIMERPROC是自定義類型,它的類型是
typedef void (CTestCallback::*CLASSTIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
除了是類成員函數外,其聲明與API需要的回調函數TIMERPROC類型相同。在需要顯式傳遞TIMERPROC的地方,不需要做轉換,因為ACCallback重載了操作符operator _TPStdFunc()
setwindows(hWnd,gwl_wndproc,m_Dmyproc) ,(應該是SetWindowLong(hWnd, GWL_WNDPROC, m_Dmyproc)吧?) 這里需要的參數是LONG,并不是偶們的函數提供的重載操作符的類型WNDPROC。只寫LONG的話是轉不過去的,因為類成員函數不能轉成一個指針,因此這里需要轉換兩次。寫法是:
SetWindowLong(hWnd, GWL_WNDPROC, (LONG)(WNDPROC)m_Dmyproc);
========================================================
最近在壇子上看到很多有關這方面的討論的文章,看來確實有再解釋一下的必要了。
1.它能實現什么?
簡而言之,它能把一個普通的類成員函數變成為一個stdcall的靜態函數。就是說從沒有this指針到獲得一個this指針。
它的用途,發散開來,可以用于:->窗口過程 ->線程過程 ->HOOK函數(特別是多線程下的HOOK函數) 可以使原來打亂程序面向對象的地方變得方便。在Delphi和C#中,類成員函數的委托調用使我們的程序變得非常簡潔,如果在下一代C++環境中也使用這種方法,想必會為VC程序員提供不少便利。
聽起來有點不可思議,但想明白了就很簡單。舉個例子:有一個CMyTimer類,它要調用SetTimer方法。它需要SetTimer回調它的成員函數CMyTimer::OnTimer。但是,在內存中,每個CMyTimer的實例是共享同一個OnTimer函數段的,在調用之前,調用方負責把this指針放在ECX里面傳給它。在不修改回調方的這種情況下似乎沒辦法取得this指針。但我們改變一個思路,我們不傳OnTimer的地址給回調方,而是為每個CMyTimer實例分配一段新的函數空間,這樣在回調時就能根據當前地址查找到自己是哪一個實例了。更簡單的,我們干脆就在這段函數空間中嵌入幾個指令,讓它自動把this指針取到放在ECX里面。這就是本方法的原理。
2.這種技術是什么?
這種技術稱之為THUNK技術。(本文貼出來后,聽別人說才知道的-___-b,我也是在VCL源代碼中找到,然后改進到我的VC程序中)。它的關鍵在于取得當前地址。用MOV ECX, EIP顯然是不可能的。于是我們用CALL指令。CALL指令會把下一條指令地址放到堆棧中。再POP它出來,就拿到了此地址。然后訪問它可以得到先前放入的this指針和函數指針。
3.要注意什么?
要注意的方面很多,一個是內存的問題,這段內存必須是有執行的權限,并且生存周期要足夠長,直到回調不再需要。還有就是調用約定的問題,使用本模板可以處理任何類型的函數,但必須是普通類成員函數轉為stdcall的約定。如果不滿足此條件,則需要自己動手寫模板。
?
轉自:http://peirenlei.javaeye.com/blog/305064
總結
以上是生活随笔為你收集整理的如何让API回调你的VC类成员函数而不是静态函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: grub2的配置文件grub.cfg详解
- 下一篇: 用 VC++ 2008 编写 Windo