Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Better way to use member function for C-style callback

0.00/5 (No votes)
25 Dec 2003 1  
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:

  1. 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 call SetTimer, it does return immediately. Our trick fails.

  2. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here