只要在函數(shù)聲明前加static就好了,哈哈哈哈哈~~~~~
。。。開個(gè)玩笑。以前確實(shí)大家都是這樣做的,在靜態(tài)的成員函數(shù)中再查找this指針,它多半是全局變量,或者是回調(diào)函數(shù)提供的附加參數(shù)。如果是前者,就會(huì)大大破壞程序的結(jié)構(gòu)。而現(xiàn)在,隨著社會(huì)生產(chǎn)力的發(fā)展,偶們已經(jīng)能做到將成員函數(shù)映射成為一個(gè)臨時(shí)的靜態(tài)函數(shù)了。本文就來(lái)演示一下這種實(shí)現(xiàn)方式。
首先需要包含一個(gè)由yzwykkldczsh同志編寫的模板類-----萬(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)該一樣,只是前者聲明為_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í),只要這樣寫:
/* 初始化回調(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)用的地方只要直接寫回調(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。