更好地使用成员函数处理 C 风格的回调






1.78/5 (6投票s)
2003年12月26日
2分钟阅读

55263

531
使用 ATL 委托技术回调成员函数的通用方法。
引言
Daniel Lohmann 深入探讨了使用成员函数作为 C 风格回调的方法(参见此处)。但是他并没有深入到足够彻底的程度,还存在一些不足之处。
- 这些 `EnumXXX` 函数在最后一个回调完成后返回。所以这个技巧很有效。
void SomeFunction() { m_nWindowCounter = 0; adapter::CB2< A, LPCTSTR, BOOL, HWND, LPARAM > adapter( this, &A::EnumWindowsCB1, "Hi all" ); ::EnumWindows( adapter.Callback, (LPARAM) &adapter ); }
适配器是在栈上构造的。如果 `EnumWindows` 函数返回,适配器会被析构,然后回调失败。幸运的是,`EnumWindows` 不会立即返回。但如果我们调用 `SetTimer`,它会立即返回。我们的技巧失效了。
- 我们必须为每个调用函数创建一个适配器,例如 `win::EnumWindows`,并且它的参数与原始函数的参数不同。
我们需要一种更好、更通用的方法。这就是它。
背景 - 一些基本信息
ATL 窗口基于一个 thunk 技巧。它稳定且可移植。它是如何实现的呢?这是 x86 CPU 上的核心 ` struct `。
#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)
它由这个函数初始化
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)); }
在注释中,有一个小错误。我不知道为什么它从 ATL 3.0 到 7.0 都存在。当我们调用 proc 指向的某个函数时,这个 `struct` 会将第一个参数修改为 `pThis`。这样,我们就可以在这个函数中使用 `pThis` 指针调用我们的成员函数。这是一个例子
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 } };
很棒的技巧,不是吗?它执行了一个 `struct`!借助这个简单的 thunk 技巧,我们可以走得更远。
我们需要什么?
除了我的例子,我还发现我们需要四样东西来回调成员函数:一个类、一个要回调的类成员函数、一个静态包装回调函数以及重要的 thunk。
如何将这些东西放到我的类中?继承 - 我认为这样做总是很有用的。
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; }
这是一个通用的方法。使用 `operator CallBackType()`,如果定义了一个名为 `CallbackProc` 的成员函数,你可以调用 `SetTimer(NULL, 0, 100, a)`
这是我的第一个想法。它确实有效,但仅在 G++ 3.3.1 上有效。VC++ 无法处理某些模板用法。我不得不使用像这样的丑陋适配器来修改它
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; }
注意!此 `CallBackAdapter` 有四个模板参数,而不是上面的三个。在源代码包中,我将它们和平地放在一起,您可以选择两种方式!
我的技术的缺陷
我们想要完美的东西,但事情总是有缺陷的。我的方法也有一些不足之处。
- 如果一个类对不同的成员函数进行两次回调,我无能为力。你需要仔细编写代码。
- 有些编译器无法编译它。
- 该实现仅在 x86 平台上有效。
- 最大的问题是,我们丢失了一个参数!但我们几乎不需要它,或者我们的类在回调之前就知道它。
- 比 Daniel Lohmann 的方法更复杂
历史
- 首次发布。