利用Thunk让C++成员函数变回调函数

申明:本文非笔者原创,原文转载自:https://www.cnblogs.com/fangkm/archive/2009/05/25/1488727.html

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;   
// mov ecx pThis
    BYTE          m_jmp;
    DWORD      m_relproc;  
// jmp  偏移地址

    BOOL  Init( DWORD_PTR proc, 
void *  pThis )
    {
        m_mov 
=   0xB9 ;    //  mov ecx
        m_this  =  PtrToUlong(pThis);
        m_jmp 
=   0xe9 ;           // jmp
        
// jmp跳转的地址为相对地址,相对地址 = 目标地址 - 当前指令下一条指令的地址
        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 [偏移地址]

使用:

typedef  void  ( __stdcall *StdCallFun )( int, int );

复制代码
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;
}


猜你喜欢

转载自blog.csdn.net/carson2005/article/details/79166521