• <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(活動(dòng)模板庫)是一套很小巧高效的COM開發(fā)庫,它本身的核心文件其實(shí)沒幾個(gè),COM相關(guān)的(主要是atlbase.h, atlcom.h),另外還有一個(gè)窗口相關(guān)的(atlwin.h), 所以拿來學(xué)習(xí)應(yīng)該是很方便的。但是因?yàn)锳TL的代碼充滿了模板和宏,內(nèi)部還夾雜著匯編,所以如果沒有比較豐富的C++模板和系統(tǒng)底層的知識(shí),一般人會(huì)看得一頭霧水。

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

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

            通過Thunk技術(shù)來調(diào)用類成員函數(shù)

            我們知道Windows窗口的消息處理函數(shù)要求是面向過程的C函數(shù),所以我們C++普通成員函數(shù)就不能作為窗口的消息處理函數(shù),所以這里的問題就是如何讓我們的C++成員函數(shù)和Windows的窗口的消息處理函數(shù)關(guān)聯(lián)起來?MFC是通過一個(gè)Map來實(shí)現(xiàn)的,而ATL選擇了更為高效的Thunk技術(shù)來實(shí)現(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 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)通過調(diào)用類成員函數(shù)Create來創(chuàng)建窗口, Create時(shí)通過
            _Module.AddCreateWndData(&m_thunk.cd, this)將this指針保存起來.

            (2)因?yàn)樽詴r(shí)將StartWindowProc設(shè)為窗口消息的回調(diào)處理函數(shù),所以第一個(gè)窗口消息會(huì)進(jìn)入到該函數(shù),在函數(shù)入口通過_Module.ExtractCreateWndData()將保存的This指針取出來。

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

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

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

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

            (5)下一個(gè)窗口消息到來,調(diào)用我們新的Thunk代碼,我們的Thunk代碼將第一個(gè)hWnd參數(shù)替換成This指針,然后跳轉(zhuǎn)到WindowProc

            (6)在WindowProc函數(shù)中的第一參數(shù)已經(jīng)被轉(zhuǎn)成This指針,接下來我們就可以根據(jù)這個(gè)This指針調(diào)用它的虛函數(shù)ProcessWindowMessage了。

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

            打開_ATL_DEBUG_INTERFACES宏來跟蹤接口的引用計(jì)數(shù)

            我們知道COM中引用計(jì)數(shù)的管理一直是個(gè)難題,因?yàn)槟愕慕涌谑谴蠹夜灿玫模绻阋糜?jì)數(shù)管理出錯(cuò),就會(huì)導(dǎo)致一些非常難查的問題,因此ATL中我們可以通過打開_ATL_DEBUG_INTERFACES宏 ,讓我們通過Debug信息察看每個(gè)接口的引用計(jì)數(shù)情況。那么ATL是如何做到的呢?

            相信用過ATL的人都會(huì)看到過這個(gè)代碼:
            struct _QIThunk
            {

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

            下面我們來分析下ATL跟蹤接口引用計(jì)數(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 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內(nèi)部是通過調(diào)用InternalQueryInterface來查詢接口(QueryInterface)的,我們看到如果定義了宏_ATL_DEBUG_INTERFACES,它會(huì)增加一行代碼 _Module.AddThunk((IUnknown**)ppvObject, pszClassName, iid)。

            (2)AddThunk會(huì)創(chuàng)建一個(gè)_QIThunk,然后把我們的指針改成它新建的_QIThunk指針,這意味著我們上面QueryInterface的得到的指針已經(jīng)被改成_QIThunk指針了, 因?yàn)槲覀兯械腃OM接口指針都是通過QueryInterface得到的,所以接下來任何COM接口的調(diào)用都會(huì)跑到_QIThunk中。

            (3)_QIThunk是嚴(yán)格按照IUnknow布局的,它虛表函數(shù)依次是QueryInterface, AddRef, Release, f3, f4, ... f1023, f1024。現(xiàn)在任何AddRef和Release的調(diào)用我們都可以攔截到了,這樣我們也就能跟蹤每個(gè)接口的引用計(jì)數(shù)情況了。

            (4)那如果調(diào)用其他接口函數(shù)怎么辦?因?yàn)樘摵瘮?shù)的調(diào)用實(shí)際上是根據(jù)虛表中索引位置來調(diào)用的,所以調(diào)用其他虛函數(shù)實(shí)際上就是調(diào)用f3, f4 ... f1024等。現(xiàn)在我們應(yīng)該知道我們這1000多個(gè)虛函數(shù)的作用了。對,他們實(shí)際上只是占位函數(shù),ATL假設(shè)任何接口都不會(huì)超過1024個(gè)方法。所以我們這些占位函數(shù)要實(shí)現(xiàn)的功能就是如何通過我們保存的原始IUnknown* pUnk, 轉(zhuǎn)去調(diào)用它真正的虛函數(shù)。

            (5)我們可以看到每個(gè)占位函數(shù)的實(shí)現(xiàn)都是一樣的,他們會(huì)去調(diào)用一段匯編代碼,我們看到這段匯編是裸代碼(naked),下面我們來分析這段匯編代碼.
            根據(jù)QIThunk的內(nèi)存布局, 前4個(gè)字節(jié)是虛表指針,4-8字節(jié)是保存的原始接口指針IUnknown* pUnk,8-12字節(jié)是引用計(jì)數(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的引用計(jì)數(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\         //將原始接口指針保存替換剛調(diào)用過來的第一參數(shù)
                __asm mov eax, dword ptr [eax]\        //取出原始接口指針保存的虛表地址,保存到eax
                __asm mov eax, dword ptr [eax+4*n]\       //根據(jù)索引,取出原始虛表中對應(yīng)的函數(shù)地址
                __asm jmp eax\        //跳轉(zhuǎn)到該函數(shù)地址
            }

            可以看到,通過上面的匯編代碼,將原來是針對QIThunk的調(diào)用又轉(zhuǎn)回到了我們原始的接口中。呵呵, 實(shí)際上應(yīng)該是
            ATL攔截了我們原始的接口調(diào)用,轉(zhuǎn)到了QIThunk中,而QIThunk最終又通過Thunk機(jī)制轉(zhuǎn)回了原始的接口調(diào)用。

            通過上面一些介紹,希望可以幫助你理解ATL, 我們可以看到Thunk本質(zhì)上只是通過匯編實(shí)現(xiàn)參數(shù)的修改和指令的跳轉(zhuǎn)。

            以前我看ATL也很吃力,以我個(gè)人的經(jīng)驗(yàn),一些東西剛開始看不太懂就放一放,先去看一些基本的東西,比如不懂COM,先去學(xué)下C++ 中的虛函數(shù);不懂C++模板,先去學(xué)下STL;不懂Thunk,先去看一下匯編,等有了一定的積累,回頭再看,一切就覺得沒這么難了。
            posted on 2012-10-23 00:23 Richard Wei 閱讀(3299) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 匯編
            久久综合鬼色88久久精品综合自在自线噜噜 | 青青国产成人久久91网| 久久99国产精品99久久| 久久久久99精品成人片牛牛影视 | 日本道色综合久久影院| 久久久久亚洲AV成人网| 综合人妻久久一区二区精品| 久久国产亚洲高清观看| 青青青青久久精品国产h久久精品五福影院1421 | 久久精品国产亚洲7777| 国产成人精品综合久久久久| 激情五月综合综合久久69| 亚洲级αV无码毛片久久精品| 久久久青草青青亚洲国产免观| 亚洲&#228;v永久无码精品天堂久久 | 亚洲精品高清久久| 亚洲国产精品一区二区久久hs| 日本精品一区二区久久久| 久久久青草青青亚洲国产免观| 日本强好片久久久久久AAA| 色综合久久88色综合天天 | 国产成人精品久久综合 | 久久亚洲精品中文字幕三区| 久久久久亚洲AV无码麻豆| 久久天天躁狠狠躁夜夜躁2014| 精品久久久久久无码人妻热| 秋霞久久国产精品电影院| 久久福利青草精品资源站| 国内精品久久久久影院优| 亚洲日韩中文无码久久| 一本久久a久久精品vr综合| 看全色黄大色大片免费久久久| 99久久国产综合精品五月天喷水| 成人久久精品一区二区三区| 香蕉久久av一区二区三区| 久久亚洲国产成人精品性色| 人妻精品久久无码区| 久久国产色AV免费看| 久久美女网站免费| 国产精品免费久久久久电影网| 久久精品国产69国产精品亚洲 |