理解ATL中的一些汇编代码
生活随笔
收集整理的這篇文章主要介紹了
理解ATL中的一些汇编代码
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
我們知道ATL(活動模板庫)是一套很小巧高效的COM開發(fā)庫,它本身的核心文件其實沒幾個,COM相關的(主要是atlbase.h, atlcom.h),另外還有一個窗口相關的(atlwin.h), 所以拿來學習應該是很方便的。但是因為ATL的代碼充滿了模板和宏,內部還夾雜著匯編,所以如果沒有比較豐富的C++模板和系統(tǒng)底層的知識,一般人會看得一頭霧水。
下面我們主要分析一下ATL中的一些匯編代碼。
ATL中出現(xiàn)匯編代碼主要是2處,一處是通過Thunk技術來調用類成員函數(shù)處理消息;還有一處是通過打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計數(shù)。
通過Thunk技術來調用類成員函數(shù)
我們知道Windows窗口的消息處理函數(shù)要求是面向過程的C函數(shù),所以我們C++普通成員函數(shù)就不能作為窗口的消息處理函數(shù),所以這里的問題就是如何讓我們的C++成員函數(shù)和Windows的窗口的消息處理函數(shù)關聯(lián)起來?MFC是通過一個Map來實現(xiàn)的,而ATL選擇了更為高效的Thunk技術來實現(xiàn)。
我們將主要代碼貼出來,然后介紹它的創(chuàng)建過程:
template?<class?TBase,?class?TWinTraits>
HWND?CWindowImplBaseT<?TBase,?TWinTraits?>::Create(HWND?hWndParent,?RECT&?rcPos,?LPCTSTR?szWindowName,
????????DWORD?dwStyle,?DWORD?dwExStyle,?UINT?nID,?ATOM?atom,?LPVOID?lpCreateParam)
{
????ATLASSERT(m_hWnd?==?NULL);
????if(atom?==?0)
????????return?NULL;
????_Module.AddCreateWndData(&m_thunk.cd,?this);
????if(nID?==?0?&&?(dwStyle?&?WS_CHILD))
????????nID?=?(UINT)this;
????HWND?hWnd?=?::CreateWindowEx(dwExStyle,?(LPCTSTR)MAKELONG(atom,?0),?szWindowName,
????????dwStyle,?rcPos.left,?rcPos.top,?rcPos.right?-?rcPos.left,
????????rcPos.bottom?-?rcPos.top,?hWndParent,?(HMENU)nID,
????????_Module.GetModuleInstance(),?lpCreateParam);
????ATLASSERT(m_hWnd?==?hWnd);
????return?hWnd;
}
static?LRESULT?CALLBACK?StartWindowProc(HWND?hWnd,?UINT?uMsg,
????????WPARAM?wParam,?LPARAM?lParam)
????{
????????CContainedWindowT<?TBase?>*?pThis?=?(CContainedWindowT<?TBase?>*)_Module.ExtractCreateWndData();
????????ATLASSERT(pThis?!=?NULL);
????????pThis->m_hWnd?=?hWnd;
????????pThis->m_thunk.Init(WindowProc,?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);
????}
class CWndProcThunk{public:union{_AtlCreateWndData cd;_WndProcThunk thunk;};void Init(WNDPROC proc, void* pThis){#if defined (_M_IX86)thunk.m_mov = 0x042444C7; ?//C7 44 24 0Cthunk.m_this = (DWORD)pThis;thunk.m_jmp = 0xe9;thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));#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 cacheFlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));}};static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){CContainedWindowT< TBase >* pThis = (CContainedWindowT< TBase >*)hWnd;ATLASSERT(pThis->m_hWnd != NULL);ATLASSERT(pThis->m_pObject != NULL);// set a ptr to this message and save the old valueMSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };const MSG* pOldMsg = pThis->m_pCurrentMsg;pThis->m_pCurrentMsg = &msg;// pass to the message map to processLRESULT lRes;BOOL bRet = pThis->m_pObject->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, pThis->m_dwMsgMapID);// restore saved value for the current messageATLASSERT(pThis->m_pCurrentMsg == &msg);pThis->m_pCurrentMsg = pOldMsg;// do the default processing if message was not handledif(!bRet){if(uMsg != WM_NCDESTROY)lRes = pThis->DefWindowProc(uMsg, wParam, lParam);else{// unsubclass, if neededLONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC);lRes = pThis->DefWindowProc(uMsg, wParam, lParam);if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc)::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc);// clear out window handlepThis->m_hWnd = NULL;}}return lRes;}
(1)通過調用類成員函數(shù)Create來創(chuàng)建窗口, Create時通過_Module.AddCreateWndData(&m_thunk.cd,?this)將this指針保存起來.
(2)因為注冊時將StartWindowProc設為窗口消息的回調處理函數(shù),所以第一個窗口消息會進入到該函數(shù),在函數(shù)入口通過_Module.ExtractCreateWndData()將保存的This指針取出來。
(3)將窗口函數(shù)WindowProc和This指針傳給Thunk進行初始化。
Thunk初始化時寫入一些匯編代碼thunk.m_mov = 0x042444C7;thunk.m_this = (DWORD)pThis;這2行代碼表示匯編代碼mov dword ptr [esp+0x4], pThis, 而esp+0x4對應的是我們的第一個參數(shù)hWnd, 所以這個代碼表示把我們的第一參數(shù)hWnd用This替代。
接下來匯編代碼thunk.m_jmp = 0xe9;?thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));表示通過相對地址JMP跳轉到WindowProc。
(4)回到StartWindowProc, 將Thunk地址作為我們新的窗口消息處理函數(shù)地址, 這樣以后有任何新的窗口消息,調用的都是我們新的Thunk代碼了。
(5)下一個窗口消息到來,調用我們新的Thunk代碼,我們的Thunk代碼將第一個hWnd參數(shù)替換成This指針,然后跳轉到WindowProc
(6)在WindowProc函數(shù)中的第一參數(shù)已經(jīng)被轉成This指針,接下來我們就可以根據(jù)這個This指針調用它的虛函數(shù)ProcessWindowMessage了。
我們可以看到ATL這種通過Thunk關聯(lián)類成員函數(shù)處理消息的方法非常高效,只是參數(shù)修改和跳轉,基本上沒有任何性能損失。
打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計數(shù)
我們知道COM中引用計數(shù)的管理一直是個難題,因為你的接口是大家共用的,如果你引用計數(shù)管理出錯,就會導致一些非常難查的問題,因此ATL中我們可以通過打開_ATL_DEBUG_INTERFACES宏 ,讓我們通過Debug信息察看每個接口的引用計數(shù)情況。那么ATL是如何做到的呢?
相信用過ATL的人都會看到過這個代碼:
struct?_QIThunk
{
????STDMETHOD(f3)();
????STDMETHOD(f4)();
????STDMETHOD(f5)();
?????????
?????????
????STDMETHOD(f1022)();
????STDMETHOD(f1023)();
????STDMETHOD(f1024)();
????????.
};里面有1000多個方法,相信很多人到現(xiàn)在還不知道這些東西有什么用,其實我以前一直也沒看懂這個東西。
下面我們來分析下ATL跟蹤接口引用計數(shù)的過程,同樣先貼代碼:
static?HRESULT?WINAPI?InternalQueryInterface(void*?pThis,
????????const?_ATL_INTMAP_ENTRY*?pEntries,?REFIID?iid,?void**?ppvObject)
????{
????????ATLASSERT(pThis?!=?NULL);
????????//?First?entry?in?the?com?map?should?be?a?simple?map?entry
????????ATLASSERT(pEntries->pFunc?==?_ATL_SIMPLEMAPENTRY);
????#if?defined(_ATL_DEBUG_INTERFACES)?||?defined(_ATL_DEBUG_QI)
????????LPCTSTR?pszClassName?=?(LPCTSTR)?pEntries[-1].dw;
????#endif?//?_ATL_DEBUG_INTERFACES
????????HRESULT?hRes?=?AtlInternalQueryInterface(pThis,?pEntries,?iid,?ppvObject);
????#ifdef?_ATL_DEBUG_INTERFACES
????????_Module.AddThunk((IUnknown**)ppvObject,?pszClassName,?iid);
????#endif?//?_ATL_DEBUG_INTERFACES
????????return?_ATLDUMPIID(iid,?pszClassName,?hRes);
????}
HRESULT AddThunk(IUnknown** pp, LPCTSTR lpsz, REFIID iid){if ((pp == NULL) || (*pp == NULL))return E_POINTER;IUnknown* p = *pp;_QIThunk* pThunk = NULL;EnterCriticalSection(&m_csObjMap);// Check if exists already for identityif (InlineIsEqualUnknown(iid)){for (int i = 0; i < m_paThunks->GetSize(); i++){if (m_paThunks->operator[](i)->pUnk == p){m_paThunks->operator[](i)->InternalAddRef();pThunk = m_paThunks->operator[](i);break;}}}if (pThunk == NULL){++m_nIndexQI;if (m_nIndexBreakAt == m_nIndexQI)DebugBreak();ATLTRY(pThunk = new _QIThunk(p, lpsz, iid, m_nIndexQI, (m_nIndexBreakAt == m_nIndexQI)));if (pThunk == NULL)return E_OUTOFMEMORY;pThunk->InternalAddRef();m_paThunks->Add(pThunk);}LeaveCriticalSection(&m_csObjMap);*pp = (IUnknown*)pThunk;return S_OK; }
struct _QIThunk{STDMETHOD(QueryInterface)(REFIID iid, void** pp){ATLASSERT(m_dwRef >= 0);return pUnk->QueryInterface(iid, pp);}STDMETHOD_(ULONG, AddRef)(){if (bBreak)DebugBreak();pUnk->AddRef();return InternalAddRef();}ULONG InternalAddRef(){if (bBreak)DebugBreak();ATLASSERT(m_dwRef >= 0);long l = InterlockedIncrement(&m_dwRef);ATLTRACE(_T("%d> "), m_dwRef);AtlDumpIID(iid, lpszClassName, S_OK);if (l > m_dwMaxRef)m_dwMaxRef = l;return l;}STDMETHOD_(ULONG, Release)();STDMETHOD(f3)();STDMETHOD(f4)();? ? ? ? ?....STDMETHOD(f1023)();STDMETHOD(f1024)();_QIThunk(IUnknown* pOrig, LPCTSTR p, const IID& i, UINT n, bool b){lpszClassName = p;iid = i;nIndex = n;m_dwRef = 0;m_dwMaxRef = 0;pUnk = pOrig;bBreak = b;bNonAddRefThunk = false;}IUnknown* pUnk;long m_dwRef;long m_dwMaxRef;LPCTSTR lpszClassName;IID iid;UINT nIndex;bool bBreak;bool bNonAddRefThunk;};
#define?IMPL_THUNK(n)\
__declspec(naked)?inline?HRESULT?_QIThunk::f##n()\
{\
????__asm?mov?eax,?[esp+4]\
????__asm?cmp?dword?ptr?[eax+8],?0\
????__asm?jg?goodref\
????__asm?call?atlBadThunkCall\
????__asm?goodref:\
????__asm?mov?eax,?[esp+4]\
????__asm?mov?eax,?dword?ptr?[eax+4]\
????__asm?mov?[esp+4],?eax\
????__asm?mov?eax,?dword?ptr?[eax]\
????__asm?mov?eax,?dword?ptr?[eax+4*n]\
????__asm?jmp?eax\
}
IMPL_THUNK(3)IMPL_THUNK(4)IMPL_THUNK(5)
....
(1)ATL內部是通過調用InternalQueryInterface來查詢接口(QueryInterface)的,我們看到如果定義了宏_ATL_DEBUG_INTERFACES,它會增加一行代碼?_Module.AddThunk((IUnknown**)ppvObject,?pszClassName,?iid)。
(2)AddThunk會創(chuàng)建一個_QIThunk,然后把我們的指針改成它新建的_QIThunk指針,這意味著我們上面QueryInterface的得到的指針已經(jīng)被改成_QIThunk指針了, 因為我們所有的COM接口指針都是通過QueryInterface得到的,所以接下來任何COM接口的調用都會跑到_QIThunk中。
(3)_QIThunk是嚴格按照IUnknow布局的,它虛表函數(shù)依次是QueryInterface,?AddRef, Release, f3, f4, ... f1023, f1024。現(xiàn)在任何AddRef和Release的調用我們都可以攔截到了,這樣我們也就能跟蹤每個接口的引用計數(shù)情況了。
(4)那如果調用其他接口函數(shù)怎么辦?因為虛函數(shù)的調用實際上是根據(jù)虛表中索引位置來調用的,所以調用其他虛函數(shù)實際上就是調用f3, f4 ... f1024等。現(xiàn)在我們應該知道我們這1000多個虛函數(shù)的作用了。對,他們實際上只是占位函數(shù),ATL假設任何接口都不會超過1024個方法。所以我們這些占位函數(shù)要實現(xiàn)的功能就是如何通過我們保存的原始IUnknown* pUnk,?轉去調用它真正的虛函數(shù)。
(5)我們可以看到每個占位函數(shù)的實現(xiàn)都是一樣的,他們會去調用一段匯編代碼,我們看到這段匯編是裸代碼(naked),下面我們來分析這段匯編代碼.
根據(jù)QIThunk的內存布局, 前4個字節(jié)是虛表指針,4-8字節(jié)是保存的原始接口指針IUnknown* pUnk,8-12字節(jié)是引用計數(shù)long m_dwRef
#define?IMPL_THUNK(n)\
__declspec(naked)?inline?HRESULT?_QIThunk::f##n()\
{\
????__asm?mov?eax,?[esp+4]\ ? ? ? //將第一參數(shù),即pQIThunk保存到eax
????__asm?cmp?dword?ptr?[eax+8],?0\ ? ? ?//判斷QIThunk的引用計數(shù)是否為0
????__asm?jg?goodref\ ? ? ? //大于0才是正確的
????__asm?call?atlBadThunkCall\
????__asm?goodref:\
????__asm?mov?eax,?[esp+4]\ ? ? ? ? //將第一參數(shù),即pQIThunk保存到eax
????__asm?mov?eax,?dword?ptr?[eax+4]\ ? ? ? ?//取出QIThunk的原始接口指針IUnknown* pUnk
????__asm?mov?[esp+4],?eax\ ? ? ? ? //將原始接口指針保存替換剛調用過來的第一參數(shù)
????__asm?mov?eax,?dword?ptr?[eax]\ ? ? ? ?//取出原始接口指針保存的虛表地址,保存到eax
????__asm?mov?eax,?dword?ptr?[eax+4*n]\ ? ? ? //根據(jù)索引,取出原始虛表中對應的函數(shù)地址
????__asm?jmp?eax\ ? ? ? ?//跳轉到該函數(shù)地址
}
可以看到,通過上面的匯編代碼,將原來是針對QIThunk的調用又轉回到了我們原始的接口中。呵呵, 實際上應該是ATL攔截了我們原始的接口調用,轉到了QIThunk中,而QIThunk最終又通過Thunk機制轉回了原始的接口調用。
通過上面一些介紹,希望可以幫助你理解ATL, 我們可以看到Thunk本質上只是通過匯編實現(xiàn)參數(shù)的修改和指令的跳轉。
以前我看ATL也很吃力,以我個人的經(jīng)驗,一些東西剛開始看不太懂就放一放,先去看一些基本的東西,比如不懂COM,先去學下C++ 中的虛函數(shù);不懂C++模板,先去學下STL;不懂Thunk,先去看一下匯編,等有了一定的積累,回頭再看,一切就覺得沒這么難了。
下面我們主要分析一下ATL中的一些匯編代碼。
ATL中出現(xiàn)匯編代碼主要是2處,一處是通過Thunk技術來調用類成員函數(shù)處理消息;還有一處是通過打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計數(shù)。
通過Thunk技術來調用類成員函數(shù)
我們知道Windows窗口的消息處理函數(shù)要求是面向過程的C函數(shù),所以我們C++普通成員函數(shù)就不能作為窗口的消息處理函數(shù),所以這里的問題就是如何讓我們的C++成員函數(shù)和Windows的窗口的消息處理函數(shù)關聯(lián)起來?MFC是通過一個Map來實現(xiàn)的,而ATL選擇了更為高效的Thunk技術來實現(xiàn)。
我們將主要代碼貼出來,然后介紹它的創(chuàng)建過程:
template?<class?TBase,?class?TWinTraits>
HWND?CWindowImplBaseT<?TBase,?TWinTraits?>::Create(HWND?hWndParent,?RECT&?rcPos,?LPCTSTR?szWindowName,
????????DWORD?dwStyle,?DWORD?dwExStyle,?UINT?nID,?ATOM?atom,?LPVOID?lpCreateParam)
{
????ATLASSERT(m_hWnd?==?NULL);
????if(atom?==?0)
????????return?NULL;
????_Module.AddCreateWndData(&m_thunk.cd,?this);
????if(nID?==?0?&&?(dwStyle?&?WS_CHILD))
????????nID?=?(UINT)this;
????HWND?hWnd?=?::CreateWindowEx(dwExStyle,?(LPCTSTR)MAKELONG(atom,?0),?szWindowName,
????????dwStyle,?rcPos.left,?rcPos.top,?rcPos.right?-?rcPos.left,
????????rcPos.bottom?-?rcPos.top,?hWndParent,?(HMENU)nID,
????????_Module.GetModuleInstance(),?lpCreateParam);
????ATLASSERT(m_hWnd?==?hWnd);
????return?hWnd;
}
static?LRESULT?CALLBACK?StartWindowProc(HWND?hWnd,?UINT?uMsg,
????????WPARAM?wParam,?LPARAM?lParam)
????{
????????CContainedWindowT<?TBase?>*?pThis?=?(CContainedWindowT<?TBase?>*)_Module.ExtractCreateWndData();
????????ATLASSERT(pThis?!=?NULL);
????????pThis->m_hWnd?=?hWnd;
????????pThis->m_thunk.Init(WindowProc,?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);
????}
class CWndProcThunk{public:union{_AtlCreateWndData cd;_WndProcThunk thunk;};void Init(WNDPROC proc, void* pThis){#if defined (_M_IX86)thunk.m_mov = 0x042444C7; ?//C7 44 24 0Cthunk.m_this = (DWORD)pThis;thunk.m_jmp = 0xe9;thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));#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 cacheFlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));}};static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){CContainedWindowT< TBase >* pThis = (CContainedWindowT< TBase >*)hWnd;ATLASSERT(pThis->m_hWnd != NULL);ATLASSERT(pThis->m_pObject != NULL);// set a ptr to this message and save the old valueMSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };const MSG* pOldMsg = pThis->m_pCurrentMsg;pThis->m_pCurrentMsg = &msg;// pass to the message map to processLRESULT lRes;BOOL bRet = pThis->m_pObject->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, pThis->m_dwMsgMapID);// restore saved value for the current messageATLASSERT(pThis->m_pCurrentMsg == &msg);pThis->m_pCurrentMsg = pOldMsg;// do the default processing if message was not handledif(!bRet){if(uMsg != WM_NCDESTROY)lRes = pThis->DefWindowProc(uMsg, wParam, lParam);else{// unsubclass, if neededLONG pfnWndProc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC);lRes = pThis->DefWindowProc(uMsg, wParam, lParam);if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnWndProc)::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc);// clear out window handlepThis->m_hWnd = NULL;}}return lRes;}
(1)通過調用類成員函數(shù)Create來創(chuàng)建窗口, Create時通過_Module.AddCreateWndData(&m_thunk.cd,?this)將this指針保存起來.
(2)因為注冊時將StartWindowProc設為窗口消息的回調處理函數(shù),所以第一個窗口消息會進入到該函數(shù),在函數(shù)入口通過_Module.ExtractCreateWndData()將保存的This指針取出來。
(3)將窗口函數(shù)WindowProc和This指針傳給Thunk進行初始化。
Thunk初始化時寫入一些匯編代碼thunk.m_mov = 0x042444C7;thunk.m_this = (DWORD)pThis;這2行代碼表示匯編代碼mov dword ptr [esp+0x4], pThis, 而esp+0x4對應的是我們的第一個參數(shù)hWnd, 所以這個代碼表示把我們的第一參數(shù)hWnd用This替代。
接下來匯編代碼thunk.m_jmp = 0xe9;?thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));表示通過相對地址JMP跳轉到WindowProc。
(4)回到StartWindowProc, 將Thunk地址作為我們新的窗口消息處理函數(shù)地址, 這樣以后有任何新的窗口消息,調用的都是我們新的Thunk代碼了。
(5)下一個窗口消息到來,調用我們新的Thunk代碼,我們的Thunk代碼將第一個hWnd參數(shù)替換成This指針,然后跳轉到WindowProc
(6)在WindowProc函數(shù)中的第一參數(shù)已經(jīng)被轉成This指針,接下來我們就可以根據(jù)這個This指針調用它的虛函數(shù)ProcessWindowMessage了。
我們可以看到ATL這種通過Thunk關聯(lián)類成員函數(shù)處理消息的方法非常高效,只是參數(shù)修改和跳轉,基本上沒有任何性能損失。
打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計數(shù)
我們知道COM中引用計數(shù)的管理一直是個難題,因為你的接口是大家共用的,如果你引用計數(shù)管理出錯,就會導致一些非常難查的問題,因此ATL中我們可以通過打開_ATL_DEBUG_INTERFACES宏 ,讓我們通過Debug信息察看每個接口的引用計數(shù)情況。那么ATL是如何做到的呢?
相信用過ATL的人都會看到過這個代碼:
struct?_QIThunk
{
????STDMETHOD(f3)();
????STDMETHOD(f4)();
????STDMETHOD(f5)();
?????????
?????????
????STDMETHOD(f1022)();
????STDMETHOD(f1023)();
????STDMETHOD(f1024)();
????????.
};里面有1000多個方法,相信很多人到現(xiàn)在還不知道這些東西有什么用,其實我以前一直也沒看懂這個東西。
下面我們來分析下ATL跟蹤接口引用計數(shù)的過程,同樣先貼代碼:
static?HRESULT?WINAPI?InternalQueryInterface(void*?pThis,
????????const?_ATL_INTMAP_ENTRY*?pEntries,?REFIID?iid,?void**?ppvObject)
????{
????????ATLASSERT(pThis?!=?NULL);
????????//?First?entry?in?the?com?map?should?be?a?simple?map?entry
????????ATLASSERT(pEntries->pFunc?==?_ATL_SIMPLEMAPENTRY);
????#if?defined(_ATL_DEBUG_INTERFACES)?||?defined(_ATL_DEBUG_QI)
????????LPCTSTR?pszClassName?=?(LPCTSTR)?pEntries[-1].dw;
????#endif?//?_ATL_DEBUG_INTERFACES
????????HRESULT?hRes?=?AtlInternalQueryInterface(pThis,?pEntries,?iid,?ppvObject);
????#ifdef?_ATL_DEBUG_INTERFACES
????????_Module.AddThunk((IUnknown**)ppvObject,?pszClassName,?iid);
????#endif?//?_ATL_DEBUG_INTERFACES
????????return?_ATLDUMPIID(iid,?pszClassName,?hRes);
????}
HRESULT AddThunk(IUnknown** pp, LPCTSTR lpsz, REFIID iid){if ((pp == NULL) || (*pp == NULL))return E_POINTER;IUnknown* p = *pp;_QIThunk* pThunk = NULL;EnterCriticalSection(&m_csObjMap);// Check if exists already for identityif (InlineIsEqualUnknown(iid)){for (int i = 0; i < m_paThunks->GetSize(); i++){if (m_paThunks->operator[](i)->pUnk == p){m_paThunks->operator[](i)->InternalAddRef();pThunk = m_paThunks->operator[](i);break;}}}if (pThunk == NULL){++m_nIndexQI;if (m_nIndexBreakAt == m_nIndexQI)DebugBreak();ATLTRY(pThunk = new _QIThunk(p, lpsz, iid, m_nIndexQI, (m_nIndexBreakAt == m_nIndexQI)));if (pThunk == NULL)return E_OUTOFMEMORY;pThunk->InternalAddRef();m_paThunks->Add(pThunk);}LeaveCriticalSection(&m_csObjMap);*pp = (IUnknown*)pThunk;return S_OK; }
struct _QIThunk{STDMETHOD(QueryInterface)(REFIID iid, void** pp){ATLASSERT(m_dwRef >= 0);return pUnk->QueryInterface(iid, pp);}STDMETHOD_(ULONG, AddRef)(){if (bBreak)DebugBreak();pUnk->AddRef();return InternalAddRef();}ULONG InternalAddRef(){if (bBreak)DebugBreak();ATLASSERT(m_dwRef >= 0);long l = InterlockedIncrement(&m_dwRef);ATLTRACE(_T("%d> "), m_dwRef);AtlDumpIID(iid, lpszClassName, S_OK);if (l > m_dwMaxRef)m_dwMaxRef = l;return l;}STDMETHOD_(ULONG, Release)();STDMETHOD(f3)();STDMETHOD(f4)();? ? ? ? ?....STDMETHOD(f1023)();STDMETHOD(f1024)();_QIThunk(IUnknown* pOrig, LPCTSTR p, const IID& i, UINT n, bool b){lpszClassName = p;iid = i;nIndex = n;m_dwRef = 0;m_dwMaxRef = 0;pUnk = pOrig;bBreak = b;bNonAddRefThunk = false;}IUnknown* pUnk;long m_dwRef;long m_dwMaxRef;LPCTSTR lpszClassName;IID iid;UINT nIndex;bool bBreak;bool bNonAddRefThunk;};
#define?IMPL_THUNK(n)\
__declspec(naked)?inline?HRESULT?_QIThunk::f##n()\
{\
????__asm?mov?eax,?[esp+4]\
????__asm?cmp?dword?ptr?[eax+8],?0\
????__asm?jg?goodref\
????__asm?call?atlBadThunkCall\
????__asm?goodref:\
????__asm?mov?eax,?[esp+4]\
????__asm?mov?eax,?dword?ptr?[eax+4]\
????__asm?mov?[esp+4],?eax\
????__asm?mov?eax,?dword?ptr?[eax]\
????__asm?mov?eax,?dword?ptr?[eax+4*n]\
????__asm?jmp?eax\
}
IMPL_THUNK(3)IMPL_THUNK(4)IMPL_THUNK(5)
....
(1)ATL內部是通過調用InternalQueryInterface來查詢接口(QueryInterface)的,我們看到如果定義了宏_ATL_DEBUG_INTERFACES,它會增加一行代碼?_Module.AddThunk((IUnknown**)ppvObject,?pszClassName,?iid)。
(2)AddThunk會創(chuàng)建一個_QIThunk,然后把我們的指針改成它新建的_QIThunk指針,這意味著我們上面QueryInterface的得到的指針已經(jīng)被改成_QIThunk指針了, 因為我們所有的COM接口指針都是通過QueryInterface得到的,所以接下來任何COM接口的調用都會跑到_QIThunk中。
(3)_QIThunk是嚴格按照IUnknow布局的,它虛表函數(shù)依次是QueryInterface,?AddRef, Release, f3, f4, ... f1023, f1024。現(xiàn)在任何AddRef和Release的調用我們都可以攔截到了,這樣我們也就能跟蹤每個接口的引用計數(shù)情況了。
(4)那如果調用其他接口函數(shù)怎么辦?因為虛函數(shù)的調用實際上是根據(jù)虛表中索引位置來調用的,所以調用其他虛函數(shù)實際上就是調用f3, f4 ... f1024等。現(xiàn)在我們應該知道我們這1000多個虛函數(shù)的作用了。對,他們實際上只是占位函數(shù),ATL假設任何接口都不會超過1024個方法。所以我們這些占位函數(shù)要實現(xiàn)的功能就是如何通過我們保存的原始IUnknown* pUnk,?轉去調用它真正的虛函數(shù)。
(5)我們可以看到每個占位函數(shù)的實現(xiàn)都是一樣的,他們會去調用一段匯編代碼,我們看到這段匯編是裸代碼(naked),下面我們來分析這段匯編代碼.
根據(jù)QIThunk的內存布局, 前4個字節(jié)是虛表指針,4-8字節(jié)是保存的原始接口指針IUnknown* pUnk,8-12字節(jié)是引用計數(shù)long m_dwRef
#define?IMPL_THUNK(n)\
__declspec(naked)?inline?HRESULT?_QIThunk::f##n()\
{\
????__asm?mov?eax,?[esp+4]\ ? ? ? //將第一參數(shù),即pQIThunk保存到eax
????__asm?cmp?dword?ptr?[eax+8],?0\ ? ? ?//判斷QIThunk的引用計數(shù)是否為0
????__asm?jg?goodref\ ? ? ? //大于0才是正確的
????__asm?call?atlBadThunkCall\
????__asm?goodref:\
????__asm?mov?eax,?[esp+4]\ ? ? ? ? //將第一參數(shù),即pQIThunk保存到eax
????__asm?mov?eax,?dword?ptr?[eax+4]\ ? ? ? ?//取出QIThunk的原始接口指針IUnknown* pUnk
????__asm?mov?[esp+4],?eax\ ? ? ? ? //將原始接口指針保存替換剛調用過來的第一參數(shù)
????__asm?mov?eax,?dword?ptr?[eax]\ ? ? ? ?//取出原始接口指針保存的虛表地址,保存到eax
????__asm?mov?eax,?dword?ptr?[eax+4*n]\ ? ? ? //根據(jù)索引,取出原始虛表中對應的函數(shù)地址
????__asm?jmp?eax\ ? ? ? ?//跳轉到該函數(shù)地址
}
可以看到,通過上面的匯編代碼,將原來是針對QIThunk的調用又轉回到了我們原始的接口中。呵呵, 實際上應該是ATL攔截了我們原始的接口調用,轉到了QIThunk中,而QIThunk最終又通過Thunk機制轉回了原始的接口調用。
通過上面一些介紹,希望可以幫助你理解ATL, 我們可以看到Thunk本質上只是通過匯編實現(xiàn)參數(shù)的修改和指令的跳轉。
以前我看ATL也很吃力,以我個人的經(jīng)驗,一些東西剛開始看不太懂就放一放,先去看一些基本的東西,比如不懂COM,先去學下C++ 中的虛函數(shù);不懂C++模板,先去學下STL;不懂Thunk,先去看一下匯編,等有了一定的積累,回頭再看,一切就覺得沒這么難了。
轉載于:https://www.cnblogs.com/weiym/archive/2012/10/23/2734798.html
總結
以上是生活随笔為你收集整理的理解ATL中的一些汇编代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中信银行app怎么取消人脸识别(中国中信
- 下一篇: 我的起源高级装备锻造台怎么解锁