Better way to use member function for C-style callback






1.78/5 (6 votes)
Dec 26, 2003
2 min read

54851

531
A generic way to callback a member function using ATL thunk technique
Introduction
Daniel Lohmann has a deep look at use member functions for C-style callbacks (see here). But he did still not deeply enough. there're some weak points:
- Those
EnumXXX
functions return after last callback finished. So this trick goes well.void SomeFunction() { m_nWindowCounter = 0; adapter::CB2< A, LPCTSTR, BOOL, HWND, LPARAM > adapter( this, &A::EnumWindowsCB1, "Hi all" ); ::EnumWindows( adapter.Callback, (LPARAM) &adapter ); }
Adapter is constructed on stack. If
EnumWindows
function returns, adapter is deconstructed then callback fails. Fortunately,EnumWindows
doesn't. If we callSetTimer
, it does return immediately. Our trick fails. - We have to make a adapter for each caller function, like
win::EnumWindows
and its parameters are not same as the original one.
We need a more better and more generic way I said. Here is it.
Background - Some basic info
ATL windows bases on a thunk trick. It's stable and portable. How does it? This is the core struct
on x86 cpu.
#pragma pack(push,1) struct _CallBackProcThunk { DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd) DWORD m_this; // BYTE m_jmp; // jmp WndProc DWORD m_relproc; // relative jmp }; #pragma pack(pop)
It's initialized by this function:
void _CallBackProcThunk::Init(DWORD_PTR proc, void* pThis) { m_mov = 0x042444C7; //C7 44 24 0C <-- this bug is funny, //correct value is 04. m_this = PtrToUlong(pThis); m_jmp = 0xe9; m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk))); // write block from data cache and // flush from instruction cache FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk)); }
In the comments, there is a small bug. I don't know why it exists from ATL 3.0 to 7.0. When we call some function pointed by proc, this struct
modified first parameter to pThis
. well, we can call our member function in that function with pThis
pointer. Here is an example:
class A{ _CallBackProcThunk thunk; //start callback here void Init(...){ thunk.Init((DWORD_PTR)StaticCallerProc, this); SomeCallback(param1,..., (CALLBACK_TYPE)&thunk); //see this } static void StaticCallerProc(HWND hWnd, ...){ //At here, hWnd is already modified with pThis; A* pThis = (A*)hWnd; pThis->MemberCallbackProc(mHWnd, ...); } void MemBerCallbackProc(HWND hWnd, ...){ // we did } };
Great hack way, is it? It EXECUTES a struct
! With this simple thunk trick, we can go farther.
What is it that we need?
Besides my example, I find four things we need to callback a member function: a class, a class member function to callback, a static wrap callback function and the important thunk.
How to put those things into my class? Inherit - I think it is always useful to do so.
template <CLASS class="" CallBackType MemCallBackType, Base,> class CallBackAdapter{ typedef CallBackAdapter SelfType; _CallBackProcThunk thunk; void Init(CallBackType proc, SelfType* pThis) { thunk.m_mov = 0x042444C7; thunk.m_this = (DWORD)pThis; thunk.m_jmp = 0xe9; thunk.m_relproc = (int)proc - ((int)this + sizeof(_CallBackProcThunk)); } CallBackType _CallBackProcAddress(void){ return (CallBackType)&thunk; } MemCallBackType mTimerProc; template <CLASS class="" Ret T5, T4, T3, , T2 T1,> static Ret CALLBACK DefaultCallBackProc( T1 p, T2 p2, T3 p3, T4 p4, T5 p5){ return (_ThisType(p)->*_MemberType(p))(0, p2, p3, p4, p5); } template <CLASS class="" Ret T4, T3, , T2 T1,> static Ret CALLBACK DefaultCallBackProc( T1 p, T2 p2, T3 p3, T4 p4){ return (_ThisType(p)->*_MemberType(p))(0, p2, p3, p4); } template <CLASS class="" Ret T3, , T2 T1,> static Ret CALLBACK DefaultCallBackProc( T1 p, T2 p2, T3 p3){ return (_ThisType(p)->*_MemberType(p))(0, p2, p3); } template <CLASS class="" Ret , T2 T1,> static Ret CALLBACK DefaultCallBackProc( T1 p, T2 p2){ return (_ThisType(p)->*_MemberType(p))(0, p2); } public: template <CLASS T> static Base* _ThisType(T pThis){ return reinterpret_cast (pThis); } template <CLASS T> static MemCallBackType _MemberType(T pThis){ return reinterpret_cast<SELFTYPE*>(pThis)->mTimerProc; } typedef MemCallBackType BaseMemCallBackType; typedef CallBackType BaseCallBackType; operator CallBackType(){ Init((CallBackType)&DefaultCallBackProc, this); mTimerProc = &Base::CallbackProc; return (CallBackType)&thunk; } CallBackType MakeCallback(MemCallBackType lpfn){ Init((CallBackType)&DefaultCallBackProc, this); mTimerProc = lpfn; return (CallBackType)&thunk; } }; #define TimerAdapter(Base) CallBackAdapter< Base, Base, \ void (Base:: * )( HWND , UINT , UINT , DWORD ), \ void (CALLBACK *)( HWND , UINT , UINT , DWORD )> struct Test : TimerAdapter(Test){ bool mQuit; void TimerProc2(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ){ mQuit = true; KillTimer(NULL, idEvent); printf("good! %d\n", idEvent); } }; int main(void){ Test a; a.mQuit = false; SetTimer(NULL, 0, 100, a.MakeCallback(&Test::TimerProc2)); MSG msg; while(!a.mQuit && GetMessage(&msg, 0, 0, 0) ){ printf("before dispatch!\n"); DispatchMessage(&msg); } return 0; }
It's a generic way. With the operator CallBackType()
you can call SetTimer(NULL, 0, 100, a)
if you defined a member function named CallbackProc
This is my first thought. It did work, but only on G++ 3.3.1. VC++ can't handle some template usage. I had to modified it with ugly adapter like this:
template <class Base> class TimerAdapter : public CallBackAdapter< Base, TimerAdapter<Base>, void (Base:: * )( HWND , UINT , UINT , DWORD ), void (CALLBACK *)( HWND , UINT , UINT , DWORD ) > { public: typedef typename TimerAdapter>Base<::BaseMemCallBackType MemCallBackType; UINT_PTR SetTimer(UINT uElapse, MemCallBackType lpTimerFunc){ return ::SetTimer(NULL, 0, uElapse, MakeCallBackProc(lpTimerFunc)); } BOOL KillTimer(UINT_PTR uIDEvent){ return ::KillTimer(NULL, uIDEvent); } //move down the static wraper static void CALLBACK DefaultCallBackProc( HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ){ (_ThisType(hwnd)->*_MemberType(hwnd))(0, uMsg, idEvent, dwTime); } }; //more simple one template <class Base> struct RasDailAdapter : public CallBackAdapter< Base, RasDailAdapter<Base>, void (Base:: * )(UINT unMsg, RASCONNSTATE rasconnstate,DWORD dwError), void (CALLBACK *)(UINT unMsg, RASCONNSTATE rasconnstate,DWORD dwError) > { static void CALLBACK CallBackProc(UINT unMsg, RASCONNSTATE rasconnstate,DWORD dwError){ (_ThisType(hwnd)->*_MemberType(hwnd))(WM_RASDIALEVENT, rasconnstate, dwError); } }; //tester struct Test : TimerAdapter<Test>{ bool mQuit; void TimerProc2(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ){ mQuit = true; KillTimer(idEvent); printf("good! %d\n", idEvent); } }; int main(void){ Test a; a.mQuit = false; a.SetTimer(100, &Test::TimerProc2) MSG msg; while(!a.mQuit && GetMessage(&msg, 0, 0, 0) ){ printf("before dispatch!\n"); DispatchMessage(&msg); } return 0; }
Caution! This CallBackAdapter
has four template parameters, not three as above. In the Source package, I made them together peacefully, you can choose both ways!
Defect of my technique
We want prefect things, but things go faulty. There are some weak points for my way.
- If a class callback twice for different member function, I can't help it. You need careful code.
- Some compilers can't compile it.
- The implementation only works on x86 platform.
- The biggest one, we lost a parameter! But we almost do NOT need it or our class knows it before callback.
- More Complex than Daniel Lohmann's way
History
- First release.