<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      厚積薄發
      海納百川,有容乃大
      我們知道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。現在任何AddRef和Release的調用我們都可以攔截到了,這樣我們也就能跟蹤每個接口的引用計數情況了。

      (4)那如果調用其他接口函數怎么辦?因為虛函數的調用實際上是根據虛表中索引位置來調用的,所以調用其他虛函數實際上就是調用f3, f4 ... f1024等。現在我們應該知道我們這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:31  Richard Wei  閱讀(2042)  評論(1)    收藏  舉報

      主站蜘蛛池模板: 亚洲精品一区二区天堂| 日韩在线视频线观看一区| 在线观看人成视频免费| 亚洲AV无码午夜嘿嘿嘿| 九九热在线免费视频观看 | 国产一区二区日韩在线| 韩国V欧美V亚洲V日本V| 18禁动漫一区二区三区| 久久中文字幕日韩无码视频 | 久久日韩在线观看视频| 乱人伦中文字幕成人网站在线| 在线亚洲人成电影网站色www| 国产中文一区卡二区不卡| 中文字幕av国产精品| 香蕉EEWW99国产精选免费| 欧洲精品码一区二区三区| 日本丰满熟妇videossexhd| 激情综合五月网| 嫖妓丰满肥熟妇在线精品 | 中文字幕无码免费久久| 国产偷自一区二区三区在线| 99中文字幕精品国产| 国产伦码精品一区二区| 亚洲国产精品综合久久网络| 天天色综网| 中文日产幕无线码一区中文 | 深夜在线观看免费av| 国产AV大陆精品一区二区三区| 日本一高清二区视频久二区| 肥东县| 激情伊人五月天久久综合| 国产亚洲精品2021自在线| 亚洲av午夜福利大精品| 草草线在成年免费视频2| 国产精品久久久国产盗摄| 99精品国产一区二区三区2021| 国产毛片精品一区二区色 | 亚洲www永久成人网站| 深水埗区| 久久精产国品一二三产品| 99精品视频在线观看婷婷|