ATL模板类库使用Thunk技术来实现与窗口消息相关联的HWND和负责处理消息的对象的this指针之间的映射。
ATL中窗口类注册时,窗口过程函数缺省值都是StartWindowProc,当创建窗口产生第一条消息时将调用此函数。 StartWindowProc是CWindowImplBase的一个静态成员函数,它的工作是建立CWindowImpl派生对象的HWND与对象的 this指针之间的映射。在新的HWND被缓存到WindowImpl派生对象的成员数据中之后,对象真正的窗口过程将替代 StartWindowProc窗口过程,并且窗口过程参数HWND被替换成对象指针值。
//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 之间的距离)。
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技术测试的例子
//涂远东 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; }