ATL中采用了一种动态生成机器指令的方式进行窗口句柄与窗口对象进行关联,以是详细分析:
CWindowImpl会在第一次调用Create时注册窗口类,该窗口类是的信息是在CWindowImpl的子类中使用
DECLARE_WND_CLASS定义的,该宏会为CWindowImpl的子类定义一个静态成员函数GetWndClassInfo,
该函数返回一个CWndClassInfo结构体,其中包含了WNDCLASSEX,用于指定该类的窗口类注册时所用的
WNDCLASSEX结构。
在DECLARE_WND_CLASS指定的默认的窗口过程是StartWindowProc,该函数是CWindowImplBaseT
的静态成员函数,用于第一次收到消息时将窗口对象与窗口句柄关联(参见下文),StartWindowProc函数
定义如下所示:
template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData(); ATLASSERT(pThis != NULL); if(!pThis) { return 0; } pThis->m_hWnd = hWnd; // Initialize the thunk. This is allocated in CWindowImplBaseT::Create, // so failure is unexpected here. pThis->m_thunk.Init(pThis->GetWindowProc(), 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. ")); #else (pOldProc); // avoid unused warning #endif return pProc(hWnd, uMsg, wParam, lParam); }
函数首先通过_AtlWinModule.ExtractCreateWndData()获取到当前正在创建的窗口的CWindowImpl对象的this指针,
实际上,在_AtlWinModule中保存了一个链表,用于保存当前正在创建窗口的窗口对象的指针,当调用CWindowImpl的
Create方法时将该对象的指针到链表中,在StartWindowProc时取出,这个链表是根据ThreadID进行关联的,所以可以
保证在多个线程创建窗口是,可以在StartWindowProc取到正确的this指针。
取到对象的this指针后,首先对其m_hWnd赋值,然后初始化该对象的thunk成员(用于实现窗口和窗口对象关联的关键
对象)。该结构定义如下:
class CWndProcThunk { public: _AtlCreateWndData cd; CStdCallThunk thunk; BOOL Init(WNDPROC proc, void* pThis) { return thunk.Init((DWORD_PTR)proc, pThis); } WNDPROC GetWNDPROC() { return (WNDPROC)thunk.GetCodeAddress(); } }; struct _AtlCreateWndData { void* m_pThis; DWORD m_dwThreadID; _AtlCreateWndData* m_pNext; }; #if defined(_M_IX86) || defined (_M_AMD64) #pragma pack(push,8) class CDynamicStdCallThunk { public: _stdcallthunk *pThunk; CDynamicStdCallThunk() { pThunk = NULL; } ~CDynamicStdCallThunk() { if (pThunk) { delete pThunk; } } BOOL Init(DWORD_PTR proc, void *pThis) { if (pThunk == NULL) { pThunk = new _stdcallthunk; if (pThunk == NULL) { return FALSE; } } return pThunk->Init(proc, pThis); } void* GetCodeAddress() { return pThunk->GetCodeAddress(); } }; #pragma pack(pop) typedef CDynamicStdCallThunk CStdCallThunk; #else typedef _stdcallthunk CStdCallThunk; #endif
CWndProcThunk中的成员cd就是上文所述的_AtlWinModule.ExtractCreateWndData()中保存的创建信息,成员thunk用于窗口和窗口对象关联。定义如下:
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); } };
该结构实际上是一段代码,用于替换真正的窗口过程,该代码被StartWindowProc通过SetWindowLongPtr设置为窗口的窗口过程,由于系统调用窗口过程是采用的是stdcall,
所以会将窗口过程的参数从逆序压栈,窗口过程的原型如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
所以在调用m_thunk时栈内容如下:
[esp + 00]-->| |
[esp + 04]-->| HWND |
[esp + 08]-->|message|
[esp + 0C]-->|wParam |
[esp + 10]-->|lParam |
thunk中的代码等效于
mov dword ptr [esp+0x4], pThis
jmp WndProc
实际上就是把栈上hwnd参数修改为窗口对象的this指针,并跳转到窗口对象的WindowProc函数
(默认为CWindowImplBaseT< TBase, TWinTraits >::WindowProc,是个静态函数)。
而thunk的成员pThis和m_realproc是在StartWindowProc中初始化化的。
这样在下次系统调用窗口过程时,就会支持thunk的代码,并跳转到指定的WindowProc函数中,
在WindowProc中会将hwnd转化为this指针,并调用对应的对象进行消息处理。
CWindowImplBaseT< TBase, TWinTraits >::WindowProc定义如下:
template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd; // set a ptr to this message and save the old value _ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam); const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg; pThis->m_pCurrentMsg = &msg; // pass to the message map to process LRESULT lRes; BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0); // restore saved value for the current message ATLASSERT(pThis->m_pCurrentMsg == &msg); // do the default processing if message was not handled if(!bRet) { if(uMsg != WM_NCDESTROY) lRes = pThis->DefWindowProc(uMsg, wParam, lParam); else { // unsubclass, if needed LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC); lRes = pThis->DefWindowProc(uMsg, wParam, lParam); if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc) ::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc); // mark window as destryed pThis->m_dwState |= WINSTATE_DESTROYED; } } if((pThis->m_dwState & WINSTATE_DESTROYED) && pOldMsg== NULL) { // clear out window handle HWND hWndThis = pThis->m_hWnd; pThis->m_hWnd = NULL; pThis->m_dwState &= ~WINSTATE_DESTROYED; // clean up after window is destroyed pThis->m_pCurrentMsg = pOldMsg; pThis->OnFinalMessage(hWndThis); }else { pThis->m_pCurrentMsg = pOldMsg; } return lRes; }