Click here to Skip to main content
15,867,895 members
Articles / Desktop Programming / ATL
Article

Better way to use member function for C-style callback

Rate me:
Please Sign up or sign in to vote.
1.78/5 (6 votes)
25 Dec 20032 min read 54.2K   529   15   7
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<BASE, CallBackType MemCallBackType,>{

    typedef CallBackAdapter<BASE, CallBackType MemCallBackType,>    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<BASE*>(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


Written By
Korea (Republic of) Korea (Republic of)
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhat about Portability? Pin
Whoever26-Dec-03 5:16
Whoever26-Dec-03 5:16 
AnswerRe: What about Portability? Pin
Member 68449427-Dec-03 2:08
Member 68449427-Dec-03 2:08 
GeneralUse __thiscall Pin
Matthias Mann26-Dec-03 3:24
Matthias Mann26-Dec-03 3:24 
GeneralRe: Use __thiscall Pin
Member 68449426-Dec-03 22:11
Member 68449426-Dec-03 22:11 
GeneralRe: Use __thiscall Pin
Member 68449426-Dec-03 23:00
Member 68449426-Dec-03 23:00 
GeneralRe: Use __thiscall Pin
Matthias Mann27-Dec-03 5:42
Matthias Mann27-Dec-03 5:42 
GeneralRe: Use __thiscall Pin
NormDroid27-Dec-03 2:33
professionalNormDroid27-Dec-03 2:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.