• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            我們知道ATL(活動模板庫)是一套很小巧高效的COM開發庫,它本身的核心文件其實沒幾個,COM相關的(主要是atlbase.h, atlcom.h),另外還有一個窗口相關的(atlwin.h), 所以拿來學習應該是很方便的。但是因為ATL的代碼充滿了模板和宏,內部還夾雜著匯編,所以如果沒有比較豐富的C++模板和系統底層的知識,一般人會看得一頭霧水。

            下面我們主要分析一下ATL中的一些匯編代碼。

            ATL中出現匯編代碼主要是2處,一處是通過Thunk技術來調用類成員函數處理消息;還有一處是通過打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計數。

            通過Thunk技術來調用類成員函數

            我們知道Windows窗口的消息處理函數要求是面向過程的C函數,所以我們C++普通成員函數就不能作為窗口的消息處理函數,所以這里的問題就是如何讓我們的C++成員函數和Windows的窗口的消息處理函數關聯起來?MFC是通過一個Map來實現的,而ATL選擇了更為高效的Thunk技術來實現。

            我們將主要代碼貼出來,然后介紹它的創建過程:
            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 0C
            thunk.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 cache
            FlushInstructionCache(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 value
            MSG 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 process
            LRESULT lRes;
            BOOL bRet = pThis->m_pObject->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, pThis->m_dwMsgMapID);
            // restore saved value for the current message
            ATLASSERT(pThis->m_pCurrentMsg == &msg);
            pThis->m_pCurrentMsg = pOldMsg;
            // 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 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 handle
            pThis->m_hWnd = NULL;
            }
            }
            return lRes;
            }

            (1)通過調用類成員函數Create來創建窗口, Create時通過
            _Module.AddCreateWndData(&m_thunk.cd, this)將this指針保存起來.

            (2)因為注冊時將StartWindowProc設為窗口消息的回調處理函數,所以第一個窗口消息會進入到該函數,在函數入口通過_Module.ExtractCreateWndData()將保存的This指針取出來。

            (3)將窗口函數WindowProc和This指針傳給Thunk進行初始化。

            Thunk初始化時寫入一些匯編代碼thunk.m_mov = 0x042444C7;thunk.m_this = (DWORD)pThis;這2行代碼表示匯編代碼mov dword ptr [esp+0x4], pThis, 而esp+0x4對應的是我們的第一個參數hWnd, 所以這個代碼表示把我們的第一參數hWnd用This替代。

            接下來匯編代碼thunk.m_jmp = 0xe9; thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));表示通過相對地址JMP跳轉到WindowProc。

            (4)回到StartWindowProc, 將Thunk地址作為我們新的窗口消息處理函數地址, 這樣以后有任何新的窗口消息,調用的都是我們新的Thunk代碼了。

            (5)下一個窗口消息到來,調用我們新的Thunk代碼,我們的Thunk代碼將第一個hWnd參數替換成This指針,然后跳轉到WindowProc

            (6)在WindowProc函數中的第一參數已經被轉成This指針,接下來我們就可以根據這個This指針調用它的虛函數ProcessWindowMessage了。

            我們可以看到ATL這種通過Thunk關聯類成員函數處理消息的方法非常高效,只是參數修改和跳轉,基本上沒有任何性能損失。

            打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計數

            我們知道COM中引用計數的管理一直是個難題,因為你的接口是大家共用的,如果你引用計數管理出錯,就會導致一些非常難查的問題,因此ATL中我們可以通過打開_ATL_DEBUG_INTERFACES宏 ,讓我們通過Debug信息察看每個接口的引用計數情況。那么ATL是如何做到的呢?

            相信用過ATL的人都會看到過這個代碼:
            struct _QIThunk
            {

                STDMETHOD(f3)();
                STDMETHOD(f4)();
                STDMETHOD(f5)();
                     
                     
                STDMETHOD(f1022)();
                STDMETHOD(f1023)();
                STDMETHOD(f1024)();
                    .
            };
            里面有1000多個方法,相信很多人到現在還不知道這些東西有什么用,其實我以前一直也沒看懂這個東西。

            下面我們來分析下ATL跟蹤接口引用計數的過程,同樣先貼代碼:
                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 identity
            if (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會創建一個_QIThunk,然后把我們的指針改成它新建的_QIThunk指針,這意味著我們上面QueryInterface的得到的指針已經被改成_QIThunk指針了, 因為我們所有的COM接口指針都是通過QueryInterface得到的,所以接下來任何COM接口的調用都會跑到_QIThunk中。

            (3)_QIThunk是嚴格按照IUnknow布局的,它虛表函數依次是QueryInterface, AddRef, Release, f3, f4, ... f1023, f1024?,F在任何AddRef和Release的調用我們都可以攔截到了,這樣我們也就能跟蹤每個接口的引用計數情況了。

            (4)那如果調用其他接口函數怎么辦?因為虛函數的調用實際上是根據虛表中索引位置來調用的,所以調用其他虛函數實際上就是調用f3, f4 ... f1024等?,F在我們應該知道我們這1000多個虛函數的作用了。對,他們實際上只是占位函數,ATL假設任何接口都不會超過1024個方法。所以我們這些占位函數要實現的功能就是如何通過我們保存的原始IUnknown* pUnk, 轉去調用它真正的虛函數。

            (5)我們可以看到每個占位函數的實現都是一樣的,他們會去調用一段匯編代碼,我們看到這段匯編是裸代碼(naked),下面我們來分析這段匯編代碼.
            根據QIThunk的內存布局, 前4個字節是虛表指針,4-8字節是保存的原始接口指針IUnknown* pUnk,8-12字節是引用計數long m_dwRef

            #define IMPL_THUNK(n)\
            __declspec(naked) inline HRESULT _QIThunk::f##n()\
            {\
                __asm mov eax, [esp+4]\       //將第一參數,即pQIThunk保存到eax
                __asm cmp dword ptr [eax+8], 0\      //判斷QIThunk的引用計數是否為0
                __asm jg goodref\       //大于0才是正確的
                __asm call atlBadThunkCall\
                __asm goodref:\
                __asm mov eax, [esp+4]\         //將第一參數,即pQIThunk保存到eax
                __asm mov eax, dword ptr [eax+4]\        //取出QIThunk的原始接口指針IUnknown* pUnk
                __asm mov [esp+4], eax\         //將原始接口指針保存替換剛調用過來的第一參數
                __asm mov eax, dword ptr [eax]\        //取出原始接口指針保存的虛表地址,保存到eax
                __asm mov eax, dword ptr [eax+4*n]\       //根據索引,取出原始虛表中對應的函數地址
                __asm jmp eax\        //跳轉到該函數地址
            }

            可以看到,通過上面的匯編代碼,將原來是針對QIThunk的調用又轉回到了我們原始的接口中。呵呵, 實際上應該是
            ATL攔截了我們原始的接口調用,轉到了QIThunk中,而QIThunk最終又通過Thunk機制轉回了原始的接口調用。

            通過上面一些介紹,希望可以幫助你理解ATL, 我們可以看到Thunk本質上只是通過匯編實現參數的修改和指令的跳轉。

            以前我看ATL也很吃力,以我個人的經驗,一些東西剛開始看不太懂就放一放,先去看一些基本的東西,比如不懂COM,先去學下C++ 中的虛函數;不懂C++模板,先去學下STL;不懂Thunk,先去看一下匯編,等有了一定的積累,回頭再看,一切就覺得沒這么難了。
            posted on 2012-10-23 00:23 Richard Wei 閱讀(3278) 評論(0)  編輯 收藏 引用 所屬分類: 匯編
            国产精品99久久久精品无码| 久久99久久无码毛片一区二区| 国产高清国内精品福利99久久| 无码人妻久久一区二区三区免费丨 | 一级女性全黄久久生活片免费 | 国产精品久久一区二区三区| 日韩av无码久久精品免费| 久久久久精品国产亚洲AV无码| 亚洲国产精品嫩草影院久久| 久久精品国产亚洲av瑜伽| 国产精品美女久久久久AV福利 | 久久亚洲国产精品123区| 久久国产综合精品五月天| 日韩精品无码久久一区二区三| 伊人精品久久久久7777| 中文字幕无码免费久久| 久久久精品2019免费观看 | 国产精品一区二区久久不卡| 国产精品久久久久天天影视| 国产69精品久久久久9999| 国产福利电影一区二区三区久久老子无码午夜伦不 | 国产精品久久婷婷六月丁香| 亚洲AV无码成人网站久久精品大| 国内精品伊人久久久久| 91秦先生久久久久久久| 青青草国产97免久久费观看| 香蕉久久av一区二区三区| 99久久精品国产一区二区三区 | 人妻久久久一区二区三区| 久久香蕉国产线看观看精品yw| 国产精品日韩欧美久久综合| 一本久道久久综合狠狠躁AV| 国产成人久久AV免费| 亚洲&#228;v永久无码精品天堂久久| 久久精品日日躁夜夜躁欧美| 久久99毛片免费观看不卡| 蜜桃麻豆WWW久久囤产精品| 中文字幕亚洲综合久久| 亚洲人成伊人成综合网久久久| 精品久久国产一区二区三区香蕉| 无码超乳爆乳中文字幕久久|