ATL模板类库使用Thunk技术来实现与窗口消息相关联的HWND和负责处理消息的对象的this指针之间的映射。 ATL中窗口类注册时,窗口过程函数缺省值都是StartWindowProc,当创建窗口产生第一条消息时将调用此函数。 StartWindowProc是CWindowImplBase的一个静态成员函数,它的工作是建立CWindowImpl派生对象的HWND与对象的 this指针之间的映射。在新的HWND被缓存到WindowImpl派生对象的成员数据中之后,对象真正的窗口过程将替代 StartWindowProc窗口过程,并且窗口过程参数HWND被替换成对象指针值。
[cpp] view plain copy
//for X86)
PVOID __stdcall __AllocStdCallThunk(VOID);
VOID __stdcall __FreeStdCallThunk(PVOID);
#pragma pack(push,1)
struct _stdcallthunk
{
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
BOOL Init(DWORD_PTR proc, void* pThis)
{
m_mov = 0x042444C7; //C7 44 24 0C
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));
return TRUE;
}
//some thunks will dynamically allocate the memory for the code
void* GetCodeAddress()
{
return this;
}
void* operator new(size_t)
{
return __AllocStdCallThunk();
}
void operator delete(void* pThunk)
{
__FreeStdCallThunk(pThunk);
}
};
#pragma pack(pop)
在 Init() 函数中这组汇编指令被初始化为下面的指令:
mov dword ptr [esp+0x4], pThis
jmp (int)proc - ((int)this+sizeof(_WndProcThunk))
它完成的功能是,用窗口类的指针 pThis 代替窗口句柄 hWnd ( esp+0x4 中放的就是 hWnd ),然后跳转到传入的 proc 函数处( (int)proc - ((int)this+sizeof(_WndProcThunk)) 是 proc 与 thunk 之间的距离)。
[cpp] view plain copy
static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
CContainedWindowT< TBase >* pThis = (CContainedWindowT< TBase >*)_AtlWinModule.ExtractCreateWndData();
ATLASSERT(pThis != NULL);
if(!pThis)
{
return 0;
}
pThis->m_hWnd = hWnd;
// Initialize the thunk. This was allocated in CContainedWindowT::Create,
// so failure is unexpected here.
pThis->m_thunk.Init(WindowProc, pThis);
WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
#ifdef _DEBUG
// check if somebody has subclassed us already since we discard it
if(pOldProc != StartWindowProc)
ATLTRACE(atlTraceWindowing, 0, _T("Subclassing through a hook discarded./n"));
#else
pOldProc; // avoid unused warning
#endif
return pProc(hWnd, uMsg, wParam, lParam);
}
以下是关于Thunk技术测试的例子
[cpp] view plain copy
//涂远东 2009 09 17 深圳
//声明函数类型。
typedef void (*TESTFUN)(void*);
//定义修改代码的结构。
#pragma pack(push,1)
struct Thunk
{
DWORD m_mov; // 修改参数指令
DWORD m_this; //修改后的参数
BYTE m_jmp; // jmp TESTFUN,跳转指令。
DWORD m_relproc; // relative jmp,相对跳转的位置。
//初始化跳转代码。
void Init(TESTFUN pFun, void* pThis)
{
//设置参数指令
m_mov = 0x042444C7; //C7 44 24 0C
//设置修改后的参数
m_this = PtrToUlong(pThis);
//设置跳转指针。
m_jmp = 0xe9;
//设置跳转的相对地址。
m_relproc = (int)pFun - ((int)this+sizeof(Thunk));
//把CPU里的缓冲数据写到主内存。
FlushInstructionCache(GetCurrentProcess(),
this, sizeof(Thunk));
}
};
#pragma pack(pop)
//测试动态修改内存里的指令数据。
class CTest
{
public:
//保存动态修改代码的内存。
Thunk m_Thunk;
//真实运行的函数。
static void TestFun(void* p)
{
CTest* pTest = (CTest*)p;
pTest->Print();
}
void Print()
{
printf("这里仅仅是一个测试/n TestFun函数的参数被修改了/n");
}
};
int main(int argc, char* argv[])
{
//如下调用这个类:
//测试运行。
CTest Test;
Test.m_Thunk.Init(Test.TestFun,&Test);
TESTFUN pTestFun = (TESTFUN)&(Test.m_Thunk);
char* psz = "test";
pTestFun((void*)psz);
return 0;
}