65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.78/5 (6投票s)

2003年12月26日

2分钟阅读

viewsIcon

55263

downloadIcon

531

使用 ATL 委托技术回调成员函数的通用方法。

引言

Daniel Lohmann 深入探讨了使用成员函数作为 C 风格回调的方法(参见此处)。但是他并没有深入到足够彻底的程度,还存在一些不足之处。

  1. 这些 `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`,它会立即返回。我们的技巧失效了。

  2. 我们必须为每个调用函数创建一个适配器,例如 `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 的方法更复杂

历史

  • 首次发布。
© . All rights reserved.