程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 利用Thunk讓C++成員函數變回調函數

利用Thunk讓C++成員函數變回調函數

編輯:關於C++

Windows API經常需要回調函數,而在C++開發中面向對象當行其道,若能讓C++類的成員函數成為回調函數,簡直就是大善!但是C++成員函數都隱含了一個this指針用於指向當前的對象。要實現回調確實不容易。

我大約一年前就接觸到Thunk技術,甚至也看過利用Thunk實現將成員函數變成回調函數的例子。但是我實在沒了解過C++匯編後的樣子,很容易鑽了牛角尖,看都看不懂,直接用他們的程序又不敢,畢竟出錯後不好處理。前端時間偶爾想起Thunk技術,對未懂技術老這樣懸著很可能影響自己的程序員生涯的,於是決心閉關參悟(沒辦法,資質差啊),終於弄明白了。那種感覺啊,就像誠信禮佛的人突然見到如來一樣,或者換了貼近自己的比喻:就像千年色鬼見到美女一樣的興奮。 我忍不住的模仿小說中的修真人士突悟大道後的感歎:原來如此!

下面的分享一下我的收獲,基本上是出入門徑的寫給初學者的,大俠千萬要止步,小弟皮薄!

稍微研究了一下C++匯編後的代碼,一般調用C++的成員函數之前,都是使用ECX寄存器保存對象的指針,好在C++成員函數的調用約定__thiscall的參數壓棧順序和堆棧平衡的維護都是和回調函數的調用約定__stdcall一樣,所以只需要構造匯編將對象指針保存在ECX寄存器後JMP到成員函數的執行地址就可以了。先寫個C++結構拼湊這兩條匯編碼:

#pragma pack( push, 1 )
struct  MemFunToStdCallThunk
{
    BYTE      m_mov;
    DWORD      m_this;
    BYTE      m_jmp;
    DWORD      m_relproc;

    BOOL  Init( DWORD_PTR proc, void* pThis )
    {
        m_mov = 0xB9;
        m_this = PtrToUlong(pThis);
        m_jmp = 0xe9;
        m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(MemFunToStdCallThunk)));
        ::FlushInstructionCache( ::GetCurrentProcess(), this, sizeof(MemFunToStdCallThunk) );
        return TRUE;
    }

    void* GetCodeAddress()
    {
        return this;
    }
};
#pragma  pack( pop )

這個結構相當於兩條匯編語句:

mov ecx, pThis

jmp [偏移地址]

使用:

class  CTestClass
{
private:
    int  m_nBase;
    MemFunToStdCallThunk  m_thunk;

    void  memFun( int m, int n )
    {
        int  nSun = m_nBase + m + n;
        CString str;
        str.Format( _T("%d"), nSun );
        AtlMessageBox( NULL, _U_STRINGorID( str ) );
    }

public:
    CTestClass()
    {
        m_nBase  = 10;
    }

    void  Test()
    {
        //UnionCastType:利用聯合將函數指針轉換成DWORD_PTR
        m_thunk.Init( UnionCastType<DWORD_PTR>(&CTestClass::memFun), this );
        StdCallFun fun = (StdCallFun)m_thunk.GetCodeAddress();
        ATLASSERT( fun != NULL );
        fun( 1, 3 );
    }
};

MemFunToStdCallThunk的Init方法接受成員函數指針和對象指針後就構造成2條匯編碼,當調用fun(1,3)時,

首先將參數3和1壓入堆棧,之後跳轉到m_thunk處,也就是那構造的2條匯編碼處,將對象指針保存到ECX寄存器,之後跳轉到指定的成員函數處執行。一切OK了。

UnionCastType方法的代碼如下:

template< typename TDst, typename TSrc >
TDst  UnionCastType( TSrc src )
{
    union
    {
     TDst  uDst;
     TSrc  uSrc;
    }uMedia;
    uMedia.uSrc  =  src;
    return uMedia.uDst;
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved