• [WTL]use thunk in ATL to invoke Cstyle CALLBACK function



    By solotny
    A generic way to callback a member function using ATL thunk technique
    Download source - 5.56 Kb

    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


  • 相关阅读:
    使用a标签制作tooltips
    使用editorconfig配置你的编辑器
    JointJS绘制流程图
    用highcharts展现你的数据
    css段落首字母下沉
    sklearn框架的系统学习
    numpy删除二维数据矩阵的行和列
    sklearn中机器学习算法评价指标
    sklearn调用逻辑回归算法
    sklearn调用多项式回归
  • 原文地址:https://www.cnblogs.com/huqingyu/p/216001.html
Copyright © 2020-2023  润新知