界面庫技術概述

界面庫技術概述
  function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:'):(d.getSelection?d.getSelection():');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}
  界面庫技術概述
  在做“HOOK文件打開/保存對話框”的過程中,我首先研究了界面庫的相關知識。界 面庫一般都是由C/C++這種中低級語言編碼,這是因爲在Windows下的界面庫實現技術大都以直接操作控制Windows的消息和調用Windows 的API爲主,這就是這種中低級語言的優勢了。無論何種界面庫,最爲根本的原理就是獲得或者截獲窗口的某些消息,按照自己的需要處理這些消息,畫出自己需 要的界面。
  按照Windows下的界面庫的使用方法來分類,可以分爲兩種:
  1、 通過派生、繼承界面庫中的類來使用庫。這類界面庫現在是佔絕大多數。這類界面庫通常可以對同種類型的控件、窗口自己控制顯示風格。這種類型的界面庫典型的代表就是GuiToolkit、ProfUIS。
  2、 通過Link頭文件,使用DLL來使用的界面庫。這類界面庫一般都是商業化的界面庫。這類界面庫一般對於同種類型的控件、窗口都是顯示統一的風格。這種類型的界面庫的典型代表是Skin++、AppFace。
  上面的分類,其實同時也代表着兩種界面庫實現技術,也就是獲取用於自繪窗口的消息的兩種來源:
  1、 通過子類化、超類化改變窗口風格。其實就是調用Windows的API SetWindowLong或者通過類的派生和繼承來改變Windows窗口的默認的消息處理函數。
  2、 使用HOOK技術改變Windows的默認消息處理。
  一、SetWindowLong
  SetWindowLong(HWND hWnd,//需要改變UI的窗口的窗口句柄
  Int nIndex,//替換窗口的默認消息處理函數時爲GWL_WNDPROC
  Long dwNewLong)//新的默認的窗口消息處理函數
  調用這個API函數可以替換一個窗口的默認的消息處理函數,這樣就可以在新的窗口消息處理函數中截獲到目標窗口的相關消息,然後根據需要處理這些消息。這個API用於獲取當前窗口的消息,它不能獲取窗口中子窗口的消息。
  這種類型的界面庫,一般是開源的或者是提供了頭文件和Lib文件的界面庫。
  這個Windows API大家很可能很少直接調用,但是它的封裝――――SubclassWindow這個成員函數,我想大家都使用過。先讓我們看看各種類型的子類化過程中的SetWindowLong都藏在什麼地方,它們是如何工作的。
  1、 MFC下的實現
   在MFC 程序中,可以先在工程中添加一個用於子類化的窗口類,然後就可以通過ClassWizard這個工具來完成剩下的子類化的工作了。我們以一個自定義的 Button類CMyButton類來舉例。在打開ClassWizard爲我們的基於Dialog的工程中的一個Button資源添加一個對應的變量的 時候我們就可以看到可以直接定義了CMyButton類,如下圖:
  我們爲ID爲IDC_BUTTON1的一個按鈕資源定義一個類型爲CMyButton的成員變量m_MyBtn。這時候ClassWizard就會在重載的虛函數DoDataExchange中爲我們添加上一條語句,如下:
  void CMFCSampleDlg::DoDataExchange(CDataExchange* pDX)
  {
  CDialog::DoDataExchange(pDX);
  //{{AFX_DATA_MAP(CMFCSampleDlg)
  DDX_Control(pDX, IDC_BUTTON1, m_MyBtn);//這就是ClassWizard爲我們添加的
  //}}AFX_DATA_MAP
  }
  讓我們來看看DDX_Control這個函數爲我們做了什麼,在MFC的源代碼DLGDATA.CPP文件中我們能找到這個函數的源代碼:
  void AFXAPI DDX_Control(CDataExchange* pDX, int nIDC, CWnd& rControl)
  {
  if (rControl.m_hWnd == NULL) // not subclassed yet
  {
  ……
  …
  //注意看看,噢,原來SubclassWindow在這裏
  if (!rControl.SubclassWindow(hWndCtrl))
  {
  ASSERT(FALSE); // possibly trying to subclass twice?
  AfxThrowNotSupportedException();
  }
  ……
  …
  }
  }
  讓我們再看看MFC下的SubclassWindow這個成員函數的實現,在MFC的源代碼wincore.cpp中可以看到所有MFC下窗口類的基類CWnd中的SubclassWindow的實現:
  BOOL CWnd::SubclassWindow(HWND hWnd)
  {
  if (!Attach(hWnd))
  return FALSE;
  // allow any other subclassing to occur
  PreSubclassWindow();
  // now hook into the AFX WndProc
  WNDPROC* lplpfn = GetSuperWndProcAddr();
  //注意看看,原來它也是使用SetWindowLong啊
  WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,
  (DWORD)AfxGetAfxWndProc());
  ……
  …
  return TRUE;
  }
  上面的源代碼跟蹤說明,ClassWizard爲我們自動完成的子類化工作中,其實也是調用的SetWindowLong這個Windows API。當然在MFC下你也可以自己調用SubclassWindow這個成員函數去手動的完成子類化。
  2、 ATL/WTL下的實現
  在ATL的源代碼中,文件ATLWIN.H文件中,我們可以找到所有ATL窗口類的基類CwindowImplBaseT中的SubClassWindow的實現:
  template
  BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd)
  {
  ATLASSERT(m_hWnd == NULL);
  ATLASSERT(::IsWindow(hWnd));
  m_thunk.Init(GetWindowProc(), this);
  WNDPROC pProc = (WNDPROC)&(m_thunk.thunk);
  //注意看看,它也是調用SetWindowLong的!
  WNDPROC pfnWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
  if(pfnWndProc == NULL)
  return FALSE;
  m_pfnSuperWindowProc = pfnWndProc;
  m_hWnd = hWnd;
  return TRUE;
  }
  在ATL/WTL沒有MFC下的那種自動子類化的機制,如果需要子類化一般都是直接調用SubclassWindow這個成員函數。
  二、SetWindowsHookEx
  SetWindowsHookEx( int idHook,//HOOK的類型
  HOOKPROC lpfn,//HOOK的回調函數
  HINSTANCE hMod,//應用程序的實例句柄
  DWORD dwThreadId)//線程的ID
   SetWindowsHookEx 這個API可以設置很多種類型的HOOK,它們分別在不同的時間截獲線程ID爲dwThreadId的線程的不同消息。在編寫界面庫的時候一般設置類型爲 WH_CALLWNDPROC的HOOK,用以在窗口處理消息之前獲得消息並進行處理。在HOOK的CALLBACK函數中可以獲得窗口句柄,進而獲得窗 口的類型和窗口的風格,這樣就可以知道需要處理的消息類型。一般都是從截獲WM_CREATE消息開始,然後處理WM_PAINT、WM_NCPAINT 等等和UI有關的消息以達到自繪窗口UI的目的。
  使用HOOK技術的界面庫中當然也可以使用SetWindowLong技術,在C++寫 的界面庫 中一般是在獲得了窗口句柄以後,即使用SetWindowLong技術將窗口句柄關聯到一種特定的窗口類上。並且可以根據窗口的風格讓同一個類做出不同風 格的顯示效果,可惜的是現在市面上的界面庫都很少進行這種比較繁瑣的區分工作。
  這種類型的界面庫一般都是以DLL文件格式出現,現在最爲 流行的是 將界面庫封裝爲一個COM DLL,在應用程序中創建這個COM組件,獲得相關的接口,調用相關的接口函數來爲應用程序安裝HOOK。在做“Picasso風格的文件打開/保存對話 框”過程中,我們一直以Yahoo Messenger的Save對話框作爲參考,其實Yahoo Messenger就是使用的這種類型的界面庫的。Yahoo messenger的界面庫截獲了進程中所有線程的消息,並做了相關的處理,所以它可以截獲一些系統的窗口的創建消息和UI相關的消息,以達到改變 Windows系統窗口的顯示風格的目的。
  最後介紹一下幾個比較好的開源的界面庫或者例子。
  1、 ClassXP(
  http://www.yonsm.net/read.php?26
  )
  一個個人的開源界面庫,C語言寫的,不完善,使用HOOK技術。Yahoo messenger的界面庫就類似於這個界面庫,只不過Yahoo messenger做的更完善而已。
  2、 ProfUIS(
  http://www.codeproject.com/docking/prod_profuis.asp
  )
  一個部分開源的界面庫,有完善功能的商業版。比較完善,MFC寫的,使用的是SetWindowLong技術。
  3、 GuiToolkit(
  http://www.codeproject.com/library/guitoolkit.asp
  )
  一個開源的界面庫,比較完善,MFC寫的,使用的是SetWindowLong技術。
  4、 MSDN中的ControlSpy例子。
  MSDN中的一個例子,用於瞭解各種控件的消息。
  
  http://blog.51CTO.net/vcleaner/archive/2006/06/04/772724.aspx
  Trackback: http://tb.blog.51CTO.net/TrackBack.aspx?PostId=1861785
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章