分析文章来自于CSDN:
ATL中的Thunk机制学习
作者: sam111
由于文章不长,偶加了注释.为了完整性,在这儿贴一下,仅供参考.所有技术归原作者所有.偶只是加了注释.
ATL利用一系列的类来管理窗口。为了使代码尽量紧凑而高效,ATL使用了一种有趣的技术来实现与窗口消息相关联的HWND和负责处理消息的对象的this指针之间的映射。具体过程如下:
在窗口注册时声明的窗口过程为此窗口对应的窗口类的静态成员函数StartWindowProc,当第一条消息到达此函数时,其处理如下:
template
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowImplBaseT< TBase, TWinTraits >* pThis =
(CWindowImplBaseT< TBase, TWinTraits >*)_Module.ExtractCreateWndData();
ATLASSERT(pThis != NULL);
pThis->m_hWnd = hWnd;
pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);
WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
#ifdef _DEBUG
// check if somebody has subclassed us already since we discard it
if(pOldProc != StartWindowProc)
ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\n"));
#else
pOldProc; // avoid unused warning
#endif
return pProc(hWnd, uMsg, wParam, lParam);
}
它先由全局变量_Module中取得此窗口对应的窗口类的this指针,然后通过m_thunk运用汇编指令改造此窗口类的窗口过程成员函数。m_thunk是CWndProcThunk的实例,每个窗口类各有一个。它的定义如下:
class CWndProcThunk
{
public:
union
{
_AtlCreateWndData cd;
/*thunk在Init()里被改变,需要通知CPU.用FlushInstructionCache,查MSDN(GetCurrentProcess(), &thunk, sizeof(thunk));*/
_WndProcThunk thunk;
};
void Init(WNDPROC proc, void* pThis)
{
#if defined (_M_IX86)
thunk.m_mov = 0x042444C7; //C7 44 24 0C
thunk.m_this = (DWORD)pThis;
thunk.m_jmp = 0xe9;
thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk)); //至此thunk已改变,&thunk已成为新函数地址
#elif defined (_M_ALPHA)
thunk.ldah_at = (0x279f0000 | HIWORD(proc)) + (LOWORD(proc)>>15);
thunk.ldah_a0 = (0x261f0000 | HIWORD(pThis)) + (LOWORD(pThis)>>15);
thunk.lda_at = 0x239c0000 | LOWORD(proc);
thunk.lda_a0 = 0x22100000 | LOWORD(pThis);
thunk.jmp = 0x6bfc0000;
#endif
// write block from data cache and
// flush from instruction cache
FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));
}
};
其Init()函数完成对WndProcThunk结构的初始化工作。WndProcThunk结构针对X86体系的定义如下:
struct _WndProcThunk
{
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
};
结构成员中保存的是一组汇编指令。在X86体系下,在Init()函数中这组汇编指令被初始化为下面的指令:
mov dword ptr [esp+0x4], pThis
jmp (int)proc - ((int)this+sizeof(_WndProcThunk))
//Init()这一次是数值初始化.这里的代码做为窗口过程被调用. 按照windows stdcall堆栈调用规则,HWND四个字节,被压入ESP处,但call指令会再压入下一条指令的地址,所以ESP+0x4就是最后一个压入变量的地址.
它完成的功能是,用窗口类的指针pThis代替窗口句柄hWnd(esp+0x4中放的就是hWnd),然后跳转到传入的proc函数处((int)proc - ((int)this+sizeof(_WndProcThunk))是proc与thunk之间的距离)。
在调用完m_thunk.Init()函数之后,实际上就得到了经过改造的窗口过程(m_thunk.thunk),此窗口过程是窗口类的一个成员函数,它的第一个参数定义虽然是HWND,但实际上是它的类的this指针。最后使用SetWindowLong()用这个新的窗口过程替换掉原有的窗口过程(也就是StartWindowProc),以后的所有消息都会被路由给新的窗口过程。
在原文的评论后有几个问题,对理解一些代码有用:
解:偶的理解是,前一个指令不是jmp吗? 这儿就是偏移值了.是相对值而不是绝对地址.
可以自己编一个小程序,然后调试,在内存看到了汇编代码(十六进制)记下来,写到这儿也可以.其实是十六进制的数值.插入汇编代码更好的理解一些.不过就不能通过thunk发挥作用了.
人家说了,为了:
为了使代码尽量紧凑而高效,ATL使用了一种有趣的技术来实现与窗口消息相关联的HWND和负责处理消息的对象的this指针之间的映射。
这就是thunk的作用了.
另外WTL消息流程机制已经有人分析得不错,地址:
WTL流程分析:
和本篇正好互为补充.作者用不同色彩标注.虽然长了一点,还是可以接受的.呵呵