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;
}