日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Spy++原理初探

發布時間:2023/12/2 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spy++原理初探 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Spy++原理初探

http://www.vckbase.com/index.php/wv/1480.html 文章概要:
  • 用Visual Studio搞開發的朋友對Spy++這個工具一定不陌生,它可以分析窗體結構、進程和窗口消息,對開發工作有很大輔助作用。我們需要研究某個對象時,只要調出其查找窗口,拖動探測器的指針到指定窗口/控件上釋放即可。下面,筆者就和大家一起,用VC打造一個屬于自己的Spy++。

Spy++原理初探正文:

打開VC集成開發環境,建立一個基于對話框的工程。我們把這個工程取名為SpyXX。在窗體中畫上一個圖片框控件(Picture)、一個靜態文本控件(Static)、兩個復選框控件(Check Box)和一個選項卡控件(Tab Control)。界面設計如下圖。

探測器的制作需要兩個圖標文件(.ico)和一個鼠標光標文件(.cur),分別用于正常狀態下的顯示、鼠標拖出時的顯示以及拖出時的鼠標指針;這些資源哪里來啊?Spy++中就有啊,用eXeScope挖一下吧。(我是從其他軟件中挖出來的,名字好像叫超級什么霸,記不太清了,呵呵。)選項卡控件定義5個標簽頁,分別為"常規"、"樣式"、"類"、"窗口"和"消息"。每個標簽頁的內容用一個屬性頁(Property Page)對話框來制作。下面,我們按照順序描述一下開發過程。

一、探測器的制作

探測器用一個圖片框控件來顯示,正常狀態下顯示一幅有靶的圖標。當鼠標在上面按下時,顯示內容立刻換為另一幅無靶的圖標,同時鼠標指針變為靶狀。這樣,就給人一種靶心被拖出去的感覺了。通過上面的敘述,我們了解到圖片框需要響應WM_LBUTTONDOWN消息和WM_LBUTTONUP消息。而圖片框在正常狀態下只響應鼠標單擊消息BN_CLICK。所以,我們要通過子類化來響應上述兩個消息。

把圖片框的ID設為IDC_PIC,并選中其Notify屬性(否則不響應消息)。依次點擊菜單Insert->New Class,Class type選擇MFC Class,類名取為CMyPic,基類為CStatic。添加CSpyXXDlg類的私有成員變量CMyPic m_pic,在對話框的初始化過程中將其與圖片框關聯。代碼如下:

view source print? 1.BOOLCSpyXXDlg::OnInitDialog() 2.{ 3.????CDialog::OnInitDialog(); 4.????m_pic.SubclassDlgItem(IDC_PIC,this); 5.????…… 6.????returnTRUE; 7.}

在CMyPic類中,我們就可以響應鼠標左鍵按下和彈起的消息了。按Ctrl + W打開Class Wizard,選擇Message Maps標簽頁,在Class name下拉列表中選擇CMyPic。從Messages列表中分別增加WM_LBUTTONDOWN和WM_LBUTTONUP消息,并接受其缺省函數名OnLButtonDown和OnLButtonUp。圖標交換和鼠標光標交換的代碼如下:

view source print? 01.voidCMyPic::OnLButtonDown(UINTnFlags, CPoint point)? 02.{ 03.????// TODO: Add your message handler code here and/or call default 04.????SetCapture();   //鼠標捕獲 05.????HCURSORhc = LoadCursor(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDC_CURSOR1));? 06.????//IDC_CURSOR1是靶形光標資源號 07.????::SetCursor(hc); 08.????HICONhicon2 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON2));? 09.????//IDI_ICON2為無靶圖標資源號 10.????this->SetIcon(hicon2); 11.????CStatic::OnLButtonDown(nFlags, point); 12.} 13.voidCMyPic::OnLButtonUp(UINTnFlags, CPoint point)? 14.{ 15.????// TODO: Add your message handler code here and/or call default 16.????ReleaseCapture();//釋放鼠標捕獲 17.????HICONhicon1 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON1));? 18.????//IDI_ICON1是有靶圖標資源號 19.????this->SetIcon(hicon1); 20.????CStatic::OnLButtonUp(nFlags, point); 21.}

探測器外觀制作完成了。可以先運行一下,把鼠標按下后拖動試試。下面來實現其功能:獲取窗口句柄。根據鼠標位置來確定窗口需要用到API函數GetCursorPos和WindowFromPoint。此外,我們還想做到像抓圖程序那樣,鼠標移動到的地方,窗口四周會出現閃爍的矩形。這一點,我們用定時器來實現。定時器設在CSpyXXDlg類中,但要由CMyPic中的OnLButtonUp來啟動。所以,我們定義一個全局變量g_hMe將CSpyXXDlg的實例句柄保存起來。同時,被選取的窗口句柄也涉及到在多個標簽頁中顯示,所以也用全局變量g_hWnd將之保存。其余的用于顯示標簽頁的屬性頁對話框句柄分別用g_hPage0、g_hPage1、g_hPage2、g_hPage3和g_hPage4來保存。啟動定時器的代碼如下:

view source print? 1.FromHandle(g_hMe)->SetTimer(1,600,NULL);

在定時器中,我們要實現桌面范圍內的矩形繪制。代碼如下:

view source print? 01.POINT pnt; 02.RECT rc; 03.HWNDDeskHwnd = ::GetDesktopWindow(); //取得桌面句柄 04.HDCDeskDC = ::GetWindowDC(DeskHwnd); //取得桌面設備場景 05.intoldRop2 = SetROP2(DeskDC, R2_NOTXORPEN); 06.::GetCursorPos(&pnt);//取得鼠標坐標 07.HWNDUnHwnd = ::WindowFromPoint(pnt) ; //取得鼠標指針處窗口句柄 08.g_hWnd=UnHwnd; 09.::GetWindowRect(g_hWnd, &rc);//獲得窗口矩形 10.if( rc.left < 0 ) rc.left = 0; 11.if(rc.top < 0 ) rc.top = 0; 12.HPENnewPen = ::CreatePen(0, 3, 0); //建立新畫筆,載入DeskDC 13.HGDIOBJoldPen = ::SelectObject(DeskDC, newPen); 14.::Rectangle(DeskDC, rc.left, rc.top, rc.right, rc.bottom);//在窗口周圍顯示閃爍矩形 15.Sleep(400);//設置閃爍時間間隔 16.::Rectangle( DeskDC, rc.left, rc.top, rc.right, rc.bottom); 17.::SetROP2(DeskDC, oldRop2); 18.::SelectObject( DeskDC, oldPen); 19.::DeleteObject(newPen); 20.::ReleaseDC( DeskHwnd, DeskDC); 21.DeskDC = NULL;

到此,探測器功能全部完成。

二、兩個復選框

第一個復選框是"總在最上面",代碼如下:

view source print? 1.voidCSpyXXDlg::OnChktop()? 2.{ 3.????intnTop=((CButton*)GetDlgItem(IDC_CHKTOP))->GetCheck(); 4.????if(nTop==1) 5.????????:: SetWindowPos(m_hWnd,HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 6.????else 7.????????::SetWindowPos(m_hWnd,HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 8.}

第二個復選框是"16進制"。因為其值影響到多個屬性頁對話框的內容,所以,也用一全局變量g_nHex保存之:

view source print? 1.voidCSpyXXDlg::OnChkhex()? 2.{ 3.????g_nHex=((CButton*)GetDlgItem(IDC_CHKHEX))->GetCheck(); 4.}

這里,我們還建立了一個全局函數Display,來輸出16進制和10進制時的句柄值:

view source print? 01.CString Display(intnVal) 02.{ 03.????CString str; 04.????if(g_nHex==1) 05.????{ 06.????????str.Format("%x",nVal); 07.????????str.MakeUpper(); 08.????} 09.????else 10.????????str.Format("%d",nVal); 11.????returnstr; 12.}

三、選項卡控件

選項卡控件中,5個標簽頁對應5個屬性頁對話框,與它們關聯的類分別取名為CPage0、CPage1、CPage2、CPage3、CPage4。在CSpyXXDlg中建立私有成員變量m_page0、m_page1、m_page2、m_page3、m_page4。在其初始化過程中建立這5個屬性頁對話框:

view source print? 01.m_page0.Create(IDD_OLE_PROPPAGE_LARGE,GetDlgItem(IDC_TAB1)); 02.m_page1.Create(IDD_OLE_PROPPAGE_LARGE1,GetDlgItem(IDC_TAB1)); 03.m_page2.Create(IDD_OLE_PROPPAGE_LARGE2,GetDlgItem(IDC_TAB1)); 04.m_page3.Create(IDD_OLE_PROPPAGE_LARGE3,GetDlgItem(IDC_TAB1)); 05.m_page4.Create(IDD_OLE_PROPPAGE_LARGE4,GetDlgItem(IDC_TAB1)); 06.CRect rs; 07.m_tab.GetClientRect(rs); 08.rs.top+=20; 09.rs.bottom-=3; 10.rs.left+=3; 11.rs.right-=3; 12.m_page0.MoveWindow(rs); 13.m_page1.MoveWindow(rs); 14.m_page2.MoveWindow(rs); 15.m_page3.MoveWindow(rs); 16.m_page4.MoveWindow(rs); 17.m_page0.ShowWindow(SW_SHOW); 18.m_tab.SetCurSel(0);

然后在選項卡消息TCN_SELCHANGE響應函數中控制它們的顯示:

view source print? 01.voidCSpyXXDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult) 02.{ 03.????// TODO: Add your control notification handler code here 04.????inti=m_tab.GetCurSel(); 05.????switch(i) 06.????{ 07.????case0: 08.????????m_page0.ShowWindow(SW_SHOW); 09.????????m_page1.ShowWindow(SW_HIDE); 10.????????m_page2.ShowWindow(SW_HIDE); 11.????????m_page3.ShowWindow(SW_HIDE); 12.????????m_page4.ShowWindow(SW_HIDE); 13.????????break; 14.????case1: 15.????????m_page0.ShowWindow(SW_HIDE); 16.????????m_page1.ShowWindow(SW_SHOW); 17.????????m_page2.ShowWindow(SW_HIDE); 18.????????m_page3.ShowWindow(SW_HIDE); 19.????????m_page4.ShowWindow(SW_HIDE); 20.????????break; 21.????case2: 22.????????…… 23.????default: 24.????????; 25.????} 26.????*pResult = 0; 27.}

四、常規標簽頁

常規標簽頁負責顯示窗口句柄、窗口類名、標題文本、窗口矩形、窗口ID、進程ID和程序路徑。控制其顯示或改變應在CMyPic的WM_LBUTTONUP響應函函數中進行。代碼如下:

view source print? 01.((CPage0*)FromHandle(g_hPage0))->m_editHWND.SetWindowText(Display((int)g_hWnd)); 02.charstrClass[200]="\0"; 03.::GetClassName(g_hWnd,strClass,200); 04.((CPage0*)FromHandle(g_hPage0))->m_editCLASS.SetWindowText(strClass); 05.((CPage2*)FromHandle(g_hPage2))->SetDlgItemText(IDC_EDITCLASSNAME,strClass);< 06.?????????? 07.charstrTitle[200]="\0"; 08.?::GetWindowText(g_hWnd,strTitle,200); 09.((CPage0*)FromHandle(g_hPage0))->m_editTITLE.SetWindowText (strTitle); 10.longiWNDID=GetWindowLong(g_hWnd,GWL_ID); 11.((CPage0*)FromHandle(g_hPage0))->m_editWNDID.SetWindowText(Display((int)iWNDID)); 12.????????? 13.unsignedlong iPID=0; 14.GetWindowThreadProcessId(g_hWnd,&iPID); 15.((CPage0*)FromHandle(g_hPage0))->m_editPID.SetWindowText(Display((int)iPID)); 16.?????????? 17.CString strPath; 18.strPath=getProcPath(iPID); 19.((CPage0*)FromHandle(g_hPage0))->m_editPATH.SetWindowText(strPath); 20.?????????? 21.RECT rc; 22.::GetWindowRect(g_hWnd, &rc);//獲得窗口矩形 23.CString strRect; 24.strRect.Format("(%d,%d),(%d,%d) %dx%d",rc.left,rc.top,rc.right,rc.bottom, 25.rc.right-rc.left,rc.bottom-rc.top); 26.((CPage0*)FromHandle(g_hPage0))->m_editRECT.SetWindowText(strRect);

其中,getProcPath是獲取進程文件路徑的函數。獲取進程路徑的方法有兩種。在NT系統中,我們可以用OpenProcess()函數將進程打開后,再利用EnumProcessModules()函數枚舉該進程的模塊,最后利用GetModuleFileNameEx()函數就能取得該進程的路徑;第二種方法是利用ToolHelp API中的相關函數。而后者兼容容Windows9x和NT4.0以后系統,所以采取此法。它的實現代碼如下:

view source print? 01.CString getProcPath(intPID) 02.{ 03.????HANDLEhModule; 04.????MODULEENTRY32* minfo=newMODULEENTRY32; 05.????minfo->dwSize=sizeof(MODULEENTRY32); 06.????hModule=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,PID); Module32First(hModule,minfo); 07.????CString str; 08.????str.Format("%s",minfo->szExePath); 09.????CloseHandle(hModule); 10.????if(minfo)delete minfo; 11.????returnstr;? 12.}

五、樣式標簽頁

樣式標簽頁設計如下圖:

API函數GetWindowLong可以獲取窗口樣式或擴展樣式的值。然后我們羅列出以WS_開頭的所有窗口樣式與上述樣式值做"位與"操作,如果被包含,則返回其窗口樣式,否則返回0。這樣,就可以得到窗口樣式的列表了。擴展樣式列表與樣式列表類似。相關代碼如下:

view source print? 01.CListBox* pListStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_STYLE)); 02.CListBox* pListExStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_EX_STYLE)); 03.CEdit* pEditStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_STYLE)); 04.CEdit* pEditExStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_EX_STYLE)); 05.longstyle = GetWindowLong(g_hWnd, GWL_STYLE); 06.longstyleEx= GetWindowLong(g_hWnd, GWL_EXSTYLE); 07.pEditStyle->SetWindowText(Display((int)style)); 08.pEditExStyle->SetWindowText(Display((int)styleEx)); 09.pListStyle->ResetContent();//清空樣式列表框 10.pListExStyle->ResetContent();//清空擴展樣式列表框 11.if(style & WS_BORDER) 12.????pListStyle->AddString("WS_BORDER"); 13.if( style & WS_CAPTION) 14.????pListStyle->AddString("WS_CAPTION"); 15.if( style & WS_CHILD) 16.????pListStyle->AddString("WS_CHILD"); 17.????……

六、類標簽頁

類標簽頁的設計如下圖:

類名在常規標簽頁已獲取。API函數GetClassLong可以獲取類樣式值。樣式列表的實現與窗口樣式類似,不再贅述。

七、窗口標簽頁

窗口標簽頁的設計如下圖:

在該頁中,主要用到了下面幾個API函數:GetNextWindow、GetWindow和SendMessage。這三個API函數搭配以不同的參數值可以實現不同的功能。這里沒有用GetWIndowText函數,是因為它不能取出部分系統窗口和隱藏窗口的標題。我們用SendMessage函數加WM_GETTEXT參數取代之。代碼如下:

view source print? 01.CPage3* pPage3=(CPage3*)FromHandle(g_hPage3); 02.HWNDtempHandle; 03.chartempstr[255]="\0"; 04.tempHandle = g_hWnd;//本窗口句柄 05.pPage3->SetDlgItemText(IDC_MYHWND, Display((int)tempHandle)); 06.//獲取本窗口標題 07.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 08.pPage3->SetDlgItemText(IDC_MYTITLE, tempstr); 09.//上一窗口 10.tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDPREV);? 11.pPage3->SetDlgItemText(IDC_PREHWND, Display((int)tempHandle)); 12.//獲取上一窗口標題 13.memset(tempstr,0,255); 14.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 15.pPage3->SetDlgItemText(IDC_PRETITLE, tempstr); 16.//下一窗口 17.tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDNEXT);? 18.pPage3->SetDlgItemText(IDC_NEXTHWND,Display((int)tempHandle)); 19.memset(tempstr,0,255);//獲取下一窗口標題 20.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 21.pPage3->SetDlgItemText(IDC_NEXTTITLE, tempstr); 22.?????????? 23.tempHandle = ::GetParent(g_hWnd);//父窗口 24.pPage3->SetDlgItemText(IDC_PARENTHWND, Display((int)tempHandle)); 25.memset(tempstr,0,255); 26.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 27.pPage3->SetDlgItemText(IDC_PARENTTITLE,tempstr); 28.//第一子窗口 29.tempHandle = ::GetWindow(g_hWnd, GW_CHILD);? 30.pPage3->SetDlgItemText(IDC_CHILDHWND,Display((int)tempHandle)); 31.memset(tempstr,-0,255); 32.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 33.pPage3->SetDlgItemText(IDC_CHILDTITLE,tempstr); 34.//所有者窗口 35.tempHandle = ::GetWindow(g_hWnd, GW_OWNER);? 36.Page3->SetDlgItemText(IDC_OWNERHWND,Display((int)tempHandle)); 37.memset(tempstr,0,255); 38.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 39.pPage3->SetDlgItemText(IDC_OWNERTITLE, tempstr);

八、消息標簽頁  消息標簽頁的設計如下圖:

該頁中的列表框與樣式列表框不同,它的每個列表項前都有一個復選框。這要用到類CCheckListBox。這里要再次用到子類化的知識。從本文第一段制作CMyPric過程中,我們體會到了子類化的作用,也感到了它的不便之處。這里,我們采取另外一種方法,借雞生蛋:即用Class Wizard生成相關代碼,然后再修改它。首先在該屬性頁對話框上畫一個列表控件,打開Class Wizard關聯一個CListBox類變量m_listStatus。設置列表框的Owner Draw屬性為Fixed,并選中其Has Strings選項。如下圖:

    

然后,在Page4.h中查找到m_listStatus的定義 CListBox m_listStatus并將其改為CCheckListBox m_listStatus。這樣,我們就可以使用CCheckListBox的全部函數了。

在對話框初始化過程中添加下列語句以加入各列表項:

view source print? 01.CCheckListBox* plistStatus=((CCheckListBox*)FromHandle(g_hPage4)->GetDlgItem(IDC_LISTSTATUS)); 02.plistStatus->AddString("窗口可見"); 03.plistStatus->AddString("窗口可用"); 04.plistStatus->AddString("總在最前"); 05.plistStatus->AddString("窗口只讀"); 06.plistStatus->AddString("最大化"); 07.plistStatus->AddString("最小化"); 08.plistStatus->AddString("窗口還原"); 09.plistStatus->AddString("關閉窗口"); 10.plistStatus->AddString("激活窗口");

接下來我們要判斷,當窗口/控件被選定后,哪些列表項被勾選。這個判斷過程與樣式列表的實現類似。如第一項"窗口可見",代碼如下:

view source print? 1.longstyle = GetWindowLong(g_hWnd, GWL_STYLE); 2.if( style & WS_VISIBLE ) 3.{ 4.????pListStatus->SetCheck(0,1); 5.}

其余各項詳見源代碼。 這個列表框的作用不僅僅是顯示窗口的狀態,還要在發生勾選改動時即時改變窗口狀態或激發其行為。勾選狀態改變的消息是LBN_SELCHANGE。另外,為了不使一個勾選的改變就引起所有列表項都激發一遍,我們采用switch結構,以使哪個列表項被選中就激發哪個列表項。代碼如下:

view source print? 01.voidCPage4::OnSelchangeListstatus() 02.{ 03.????// TODO: Add your control notification handler code here 04.????intn=m_listStatus.GetCurSel(); 05.????switch(n) 06.????{ 07.????case0: 08.????????if(m_listStatus.GetCheck(0)== 1 ) 09.????????????::ShowWindow(g_hWnd, SW_SHOW); 10.????????else 11.????????????::ShowWindow(g_hWnd, SW_HIDE); 12.????????break; 13.????case1: 14.????????if(m_listStatus.GetCheck(1) == 1) 15.????????????::EnableWindow(g_hWnd, TRUE); 16.????????else 17.????????????::EnableWindow(g_hWnd,FALSE); 18.????????break; 19.????case2: 20.????????if(m_listStatus.GetCheck(2) == 1) 21.????????????::SetWindowPos(g_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE); 22.????????else 23.????????????::SetWindowPos(g_hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); 24.????????break; 25.????case3: 26.????????if(m_listStatus.GetCheck(3) == 1) 27.????????????::SendMessage(g_hWnd, EM_SETREADONLY, TRUE, 0); 28.????????else 29.????????????::SendMessage(g_hWnd, EM_SETREADONLY, FALSE, 0); 30.????????break; 31.????case4: 32.????????if(m_listStatus.GetCheck(4) ==1) 33.????????{ 34.????????????::ShowWindow(g_hWnd, SW_MAXIMIZE); 35.????????????m_listStatus.SetCheck(5,0); 36.????????} 37.????????else 38.????????????::ShowWindow (g_hWnd, SW_RESTORE); 39.????????break; 40.????case5: 41.????????if(m_listStatus.GetCheck(5) == 1) 42.????????{ 43.????????????::ShowWindow(g_hWnd, SW_MINIMIZE); 44.????????????m_listStatus.SetCheck(4,0); 45.????????} 46.????????else 47.????????????::ShowWindow(g_hWnd, SW_RESTORE); 48.????????break; 49.????case6: 50.????????if(m_listStatus.GetCheck(6) ==1) 51.????????{ 52.????????????::ShowWindow (g_hWnd, SW_RESTORE); 53.????????????m_listStatus.SetCheck(6,0); 54.????????????m_listStatus.SetCheck(5,0); 55.????????????m_listStatus.SetCheck(4,0); 56.????????} 57.????????break; 58.????case7: 59.????????if(m_listStatus.GetCheck(7) ==1) 60.????????{ 61.????????????::SendMessage (g_hWnd, WM_CLOSE, 0, 0); 62.????????????m_listStatus.SetCheck(7,0); 63.????????} 64.????????break; 65.????case8: 66.????????if(m_listStatus.GetCheck(8) ==1) 67.????????{ 68.????????????::BringWindowToTop(g_hWnd); 69.????????????m_listStatus.SetCheck(8,0); 70.????????} 71.????????break; 72.????default: 73.????; 74.????} 75.}

Spy++打造完畢。回顧其過程,難點不多,細細碎碎問題不少。也難免啊,不僅要形似,咱還要神似。文中一定還有很多地方不夠周全,希望同行朋友們不吝賜教。代碼在Window XP + VC6.0中調試通過。Spy++源碼同時放在這里。歡迎訪問我的個人主頁(阿珊境界)http://www.asanscape.com,歡迎加入我們的VC討論群713035。

Spy++原理初探-原文點擊打開鏈接

正文:

打開VC集成開發環境,建立一個基于對話框的工程。我們把這個工程取名為SpyXX。在窗體中畫上一個圖片框控件(Picture)、一個靜態文本控件(Static)、兩個復選框控件(Check Box)和一個選項卡控件(Tab Control)。界面設計如下圖。

探測器的制作需要兩個圖標文件(.ico)和一個鼠標光標文件(.cur),分別用于正常狀態下的顯示、鼠標拖出時的顯示以及拖出時的鼠標指針;這些資源哪里來啊?Spy++中就有啊,用eXeScope挖一下吧。(我是從其他軟件中挖出來的,名字好像叫超級什么霸,記不太清了,呵呵。)選項卡控件定義5個標簽頁,分別為"常規"、"樣式"、"類"、"窗口"和"消息"。每個標簽頁的內容用一個屬性頁(Property Page)對話框來制作。下面,我們按照順序描述一下開發過程。

一、探測器的制作

探測器用一個圖片框控件來顯示,正常狀態下顯示一幅有靶的圖標。當鼠標在上面按下時,顯示內容立刻換為另一幅無靶的圖標,同時鼠標指針變為靶狀。這樣,就給人一種靶心被拖出去的感覺了。通過上面的敘述,我們了解到圖片框需要響應WM_LBUTTONDOWN消息和WM_LBUTTONUP消息。而圖片框在正常狀態下只響應鼠標單擊消息BN_CLICK。所以,我們要通過子類化來響應上述兩個消息。

把圖片框的ID設為IDC_PIC,并選中其Notify屬性(否則不響應消息)。依次點擊菜單Insert->New Class,Class type選擇MFC Class,類名取為CMyPic,基類為CStatic。添加CSpyXXDlg類的私有成員變量CMyPic m_pic,在對話框的初始化過程中將其與圖片框關聯。代碼如下:

view source print? 1.BOOLCSpyXXDlg::OnInitDialog() 2.{ 3.????CDialog::OnInitDialog(); 4.????m_pic.SubclassDlgItem(IDC_PIC,this); 5.????…… 6.????returnTRUE; 7.}

在CMyPic類中,我們就可以響應鼠標左鍵按下和彈起的消息了。按Ctrl + W打開Class Wizard,選擇Message Maps標簽頁,在Class name下拉列表中選擇CMyPic。從Messages列表中分別增加WM_LBUTTONDOWN和WM_LBUTTONUP消息,并接受其缺省函數名OnLButtonDown和OnLButtonUp。圖標交換和鼠標光標交換的代碼如下:

view source print? 01.voidCMyPic::OnLButtonDown(UINTnFlags, CPoint point)? 02.{ 03.????// TODO: Add your message handler code here and/or call default 04.????SetCapture();   //鼠標捕獲 05.????HCURSORhc = LoadCursor(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDC_CURSOR1));? 06.????//IDC_CURSOR1是靶形光標資源號 07.????::SetCursor(hc); 08.????HICONhicon2 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON2));? 09.????//IDI_ICON2為無靶圖標資源號 10.????this->SetIcon(hicon2); 11.????CStatic::OnLButtonDown(nFlags, point); 12.} 13.voidCMyPic::OnLButtonUp(UINTnFlags, CPoint point)? 14.{ 15.????// TODO: Add your message handler code here and/or call default 16.????ReleaseCapture();//釋放鼠標捕獲 17.????HICONhicon1 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON1));? 18.????//IDI_ICON1是有靶圖標資源號 19.????this->SetIcon(hicon1); 20.????CStatic::OnLButtonUp(nFlags, point); 21.}

探測器外觀制作完成了。可以先運行一下,把鼠標按下后拖動試試。下面來實現其功能:獲取窗口句柄。根據鼠標位置來確定窗口需要用到API函數GetCursorPos和WindowFromPoint。此外,我們還想做到像抓圖程序那樣,鼠標移動到的地方,窗口四周會出現閃爍的矩形。這一點,我們用定時器來實現。定時器設在CSpyXXDlg類中,但要由CMyPic中的OnLButtonUp來啟動。所以,我們定義一個全局變量g_hMe將CSpyXXDlg的實例句柄保存起來。同時,被選取的窗口句柄也涉及到在多個標簽頁中顯示,所以也用全局變量g_hWnd將之保存。其余的用于顯示標簽頁的屬性頁對話框句柄分別用g_hPage0、g_hPage1、g_hPage2、g_hPage3和g_hPage4來保存。啟動定時器的代碼如下:

view source print? 1.FromHandle(g_hMe)->SetTimer(1,600,NULL);

在定時器中,我們要實現桌面范圍內的矩形繪制。代碼如下:

view source print? 01.POINT pnt; 02.RECT rc; 03.HWNDDeskHwnd = ::GetDesktopWindow(); //取得桌面句柄 04.HDCDeskDC = ::GetWindowDC(DeskHwnd); //取得桌面設備場景 05.intoldRop2 = SetROP2(DeskDC, R2_NOTXORPEN); 06.::GetCursorPos(&pnt);//取得鼠標坐標 07.HWNDUnHwnd = ::WindowFromPoint(pnt) ; //取得鼠標指針處窗口句柄 08.g_hWnd=UnHwnd; 09.::GetWindowRect(g_hWnd, &rc);//獲得窗口矩形 10.if( rc.left < 0 ) rc.left = 0; 11.if(rc.top < 0 ) rc.top = 0; 12.HPENnewPen = ::CreatePen(0, 3, 0); //建立新畫筆,載入DeskDC 13.HGDIOBJoldPen = ::SelectObject(DeskDC, newPen); 14.::Rectangle(DeskDC, rc.left, rc.top, rc.right, rc.bottom);//在窗口周圍顯示閃爍矩形 15.Sleep(400);//設置閃爍時間間隔 16.::Rectangle( DeskDC, rc.left, rc.top, rc.right, rc.bottom); 17.::SetROP2(DeskDC, oldRop2); 18.::SelectObject( DeskDC, oldPen); 19.::DeleteObject(newPen); 20.::ReleaseDC( DeskHwnd, DeskDC); 21.DeskDC = NULL;

到此,探測器功能全部完成。

二、兩個復選框

第一個復選框是"總在最上面",代碼如下:

view source print? 1.voidCSpyXXDlg::OnChktop()? 2.{ 3.????intnTop=((CButton*)GetDlgItem(IDC_CHKTOP))->GetCheck(); 4.????if(nTop==1) 5.????????:: SetWindowPos(m_hWnd,HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 6.????else 7.????????::SetWindowPos(m_hWnd,HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 8.}

第二個復選框是"16進制"。因為其值影響到多個屬性頁對話框的內容,所以,也用一全局變量g_nHex保存之:

view source print? 1.voidCSpyXXDlg::OnChkhex()? 2.{ 3.????g_nHex=((CButton*)GetDlgItem(IDC_CHKHEX))->GetCheck(); 4.}

這里,我們還建立了一個全局函數Display,來輸出16進制和10進制時的句柄值:

view source print? 01.CString Display(intnVal) 02.{ 03.????CString str; 04.????if(g_nHex==1) 05.????{ 06.????????str.Format("%x",nVal); 07.????????str.MakeUpper(); 08.????} 09.????else 10.????????str.Format("%d",nVal); 11.????returnstr; 12.}

三、選項卡控件

選項卡控件中,5個標簽頁對應5個屬性頁對話框,與它們關聯的類分別取名為CPage0、CPage1、CPage2、CPage3、CPage4。在CSpyXXDlg中建立私有成員變量m_page0、m_page1、m_page2、m_page3、m_page4。在其初始化過程中建立這5個屬性頁對話框:

view source print? 01.m_page0.Create(IDD_OLE_PROPPAGE_LARGE,GetDlgItem(IDC_TAB1)); 02.m_page1.Create(IDD_OLE_PROPPAGE_LARGE1,GetDlgItem(IDC_TAB1)); 03.m_page2.Create(IDD_OLE_PROPPAGE_LARGE2,GetDlgItem(IDC_TAB1)); 04.m_page3.Create(IDD_OLE_PROPPAGE_LARGE3,GetDlgItem(IDC_TAB1)); 05.m_page4.Create(IDD_OLE_PROPPAGE_LARGE4,GetDlgItem(IDC_TAB1)); 06.CRect rs; 07.m_tab.GetClientRect(rs); 08.rs.top+=20; 09.rs.bottom-=3; 10.rs.left+=3; 11.rs.right-=3; 12.m_page0.MoveWindow(rs); 13.m_page1.MoveWindow(rs); 14.m_page2.MoveWindow(rs); 15.m_page3.MoveWindow(rs); 16.m_page4.MoveWindow(rs); 17.m_page0.ShowWindow(SW_SHOW); 18.m_tab.SetCurSel(0);

然后在選項卡消息TCN_SELCHANGE響應函數中控制它們的顯示:

view source print? 01.voidCSpyXXDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult) 02.{ 03.????// TODO: Add your control notification handler code here 04.????inti=m_tab.GetCurSel(); 05.????switch(i) 06.????{ 07.????case0: 08.????????m_page0.ShowWindow(SW_SHOW); 09.????????m_page1.ShowWindow(SW_HIDE); 10.????????m_page2.ShowWindow(SW_HIDE); 11.????????m_page3.ShowWindow(SW_HIDE); 12.????????m_page4.ShowWindow(SW_HIDE); 13.????????break; 14.????case1: 15.????????m_page0.ShowWindow(SW_HIDE); 16.????????m_page1.ShowWindow(SW_SHOW); 17.????????m_page2.ShowWindow(SW_HIDE); 18.????????m_page3.ShowWindow(SW_HIDE); 19.????????m_page4.ShowWindow(SW_HIDE); 20.????????break; 21.????case2: 22.????????…… 23.????default: 24.????????; 25.????} 26.????*pResult = 0; 27.}

四、常規標簽頁

常規標簽頁負責顯示窗口句柄、窗口類名、標題文本、窗口矩形、窗口ID、進程ID和程序路徑。控制其顯示或改變應在CMyPic的WM_LBUTTONUP響應函函數中進行。代碼如下:

view source print? 01.((CPage0*)FromHandle(g_hPage0))->m_editHWND.SetWindowText(Display((int)g_hWnd)); 02.charstrClass[200]="\0"; 03.::GetClassName(g_hWnd,strClass,200); 04.((CPage0*)FromHandle(g_hPage0))->m_editCLASS.SetWindowText(strClass); 05.((CPage2*)FromHandle(g_hPage2))->SetDlgItemText(IDC_EDITCLASSNAME,strClass);< 06.?????????? 07.charstrTitle[200]="\0"; 08.?::GetWindowText(g_hWnd,strTitle,200); 09.((CPage0*)FromHandle(g_hPage0))->m_editTITLE.SetWindowText (strTitle); 10.longiWNDID=GetWindowLong(g_hWnd,GWL_ID); 11.((CPage0*)FromHandle(g_hPage0))->m_editWNDID.SetWindowText(Display((int)iWNDID)); 12.????????? 13.unsignedlong iPID=0; 14.GetWindowThreadProcessId(g_hWnd,&iPID); 15.((CPage0*)FromHandle(g_hPage0))->m_editPID.SetWindowText(Display((int)iPID)); 16.?????????? 17.CString strPath; 18.strPath=getProcPath(iPID); 19.((CPage0*)FromHandle(g_hPage0))->m_editPATH.SetWindowText(strPath); 20.?????????? 21.RECT rc; 22.::GetWindowRect(g_hWnd, &rc);//獲得窗口矩形 23.CString strRect; 24.strRect.Format("(%d,%d),(%d,%d) %dx%d",rc.left,rc.top,rc.right,rc.bottom, 25.rc.right-rc.left,rc.bottom-rc.top); 26.((CPage0*)FromHandle(g_hPage0))->m_editRECT.SetWindowText(strRect);

其中,getProcPath是獲取進程文件路徑的函數。獲取進程路徑的方法有兩種。在NT系統中,我們可以用OpenProcess()函數將進程打開后,再利用EnumProcessModules()函數枚舉該進程的模塊,最后利用GetModuleFileNameEx()函數就能取得該進程的路徑;第二種方法是利用ToolHelp API中的相關函數。而后者兼容容Windows9x和NT4.0以后系統,所以采取此法。它的實現代碼如下:

view source print? 01.CString getProcPath(intPID) 02.{ 03.????HANDLEhModule; 04.????MODULEENTRY32* minfo=newMODULEENTRY32; 05.????minfo->dwSize=sizeof(MODULEENTRY32); 06.????hModule=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,PID); Module32First(hModule,minfo); 07.????CString str; 08.????str.Format("%s",minfo->szExePath); 09.????CloseHandle(hModule); 10.????if(minfo)delete minfo; 11.????returnstr;? 12.}

五、樣式標簽頁

樣式標簽頁設計如下圖:

API函數GetWindowLong可以獲取窗口樣式或擴展樣式的值。然后我們羅列出以WS_開頭的所有窗口樣式與上述樣式值做"位與"操作,如果被包含,則返回其窗口樣式,否則返回0。這樣,就可以得到窗口樣式的列表了。擴展樣式列表與樣式列表類似。相關代碼如下:

view source print? 01.CListBox* pListStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_STYLE)); 02.CListBox* pListExStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_EX_STYLE)); 03.CEdit* pEditStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_STYLE)); 04.CEdit* pEditExStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_EX_STYLE)); 05.longstyle = GetWindowLong(g_hWnd, GWL_STYLE); 06.longstyleEx= GetWindowLong(g_hWnd, GWL_EXSTYLE); 07.pEditStyle->SetWindowText(Display((int)style)); 08.pEditExStyle->SetWindowText(Display((int)styleEx)); 09.pListStyle->ResetContent();//清空樣式列表框 10.pListExStyle->ResetContent();//清空擴展樣式列表框 11.if(style & WS_BORDER) 12.????pListStyle->AddString("WS_BORDER"); 13.if( style & WS_CAPTION) 14.????pListStyle->AddString("WS_CAPTION"); 15.if( style & WS_CHILD) 16.????pListStyle->AddString("WS_CHILD"); 17.????……

六、類標簽頁

類標簽頁的設計如下圖:

類名在常規標簽頁已獲取。API函數GetClassLong可以獲取類樣式值。樣式列表的實現與窗口樣式類似,不再贅述。

七、窗口標簽頁

窗口標簽頁的設計如下圖:

在該頁中,主要用到了下面幾個API函數:GetNextWindow、GetWindow和SendMessage。這三個API函數搭配以不同的參數值可以實現不同的功能。這里沒有用GetWIndowText函數,是因為它不能取出部分系統窗口和隱藏窗口的標題。我們用SendMessage函數加WM_GETTEXT參數取代之。代碼如下:

view source print? 01.CPage3* pPage3=(CPage3*)FromHandle(g_hPage3); 02.HWNDtempHandle; 03.chartempstr[255]="\0"; 04.tempHandle = g_hWnd;//本窗口句柄 05.pPage3->SetDlgItemText(IDC_MYHWND, Display((int)tempHandle)); 06.//獲取本窗口標題 07.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 08.pPage3->SetDlgItemText(IDC_MYTITLE, tempstr); 09.//上一窗口 10.tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDPREV);? 11.pPage3->SetDlgItemText(IDC_PREHWND, Display((int)tempHandle)); 12.//獲取上一窗口標題 13.memset(tempstr,0,255); 14.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 15.pPage3->SetDlgItemText(IDC_PRETITLE, tempstr); 16.//下一窗口 17.tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDNEXT);? 18.pPage3->SetDlgItemText(IDC_NEXTHWND,Display((int)tempHandle)); 19.memset(tempstr,0,255);//獲取下一窗口標題 20.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 21.pPage3->SetDlgItemText(IDC_NEXTTITLE, tempstr); 22.?????????? 23.tempHandle = ::GetParent(g_hWnd);//父窗口 24.pPage3->SetDlgItemText(IDC_PARENTHWND, Display((int)tempHandle)); 25.memset(tempstr,0,255); 26.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 27.pPage3->SetDlgItemText(IDC_PARENTTITLE,tempstr); 28.//第一子窗口 29.tempHandle = ::GetWindow(g_hWnd, GW_CHILD);? 30.pPage3->SetDlgItemText(IDC_CHILDHWND,Display((int)tempHandle)); 31.memset(tempstr,-0,255); 32.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 33.pPage3->SetDlgItemText(IDC_CHILDTITLE,tempstr); 34.//所有者窗口 35.tempHandle = ::GetWindow(g_hWnd, GW_OWNER);? 36.Page3->SetDlgItemText(IDC_OWNERHWND,Display((int)tempHandle)); 37.memset(tempstr,0,255); 38.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 39.pPage3->SetDlgItemText(IDC_OWNERTITLE, tempstr);

八、消息標簽頁  消息標簽頁的設計如下圖:

該頁中的列表框與樣式列表框不同,它的每個列表項前都有一個復選框。這要用到類CCheckListBox。這里要再次用到子類化的知識。從本文第一段制作CMyPric過程中,我們體會到了子類化的作用,也感到了它的不便之處。這里,我們采取另外一種方法,借雞生蛋:即用Class Wizard生成相關代碼,然后再修改它。首先在該屬性頁對話框上畫一個列表控件,打開Class Wizard關聯一個CListBox類變量m_listStatus。設置列表框的Owner Draw屬性為Fixed,并選中其Has Strings選項。如下圖:

    

然后,在Page4.h中查找到m_listStatus的定義 CListBox m_listStatus并將其改為CCheckListBox m_listStatus。這樣,我們就可以使用CCheckListBox的全部函數了。

在對話框初始化過程中添加下列語句以加入各列表項:

view source print? 01.CCheckListBox* plistStatus=((CCheckListBox*)FromHandle(g_hPage4)->GetDlgItem(IDC_LISTSTATUS)); 02.plistStatus->AddString("窗口可見"); 03.plistStatus->AddString("窗口可用"); 04.plistStatus->AddString("總在最前"); 05.plistStatus->AddString("窗口只讀"); 06.plistStatus->AddString("最大化"); 07.plistStatus->AddString("最小化"); 08.plistStatus->AddString("窗口還原"); 09.plistStatus->AddString("關閉窗口"); 10.plistStatus->AddString("激活窗口");

接下來我們要判斷,當窗口/控件被選定后,哪些列表項被勾選。這個判斷過程與樣式列表的實現類似。如第一項"窗口可見",代碼如下:

view source print? 1.longstyle = GetWindowLong(g_hWnd, GWL_STYLE); 2.if( style & WS_VISIBLE ) 3.{ 4.????pListStatus->SetCheck(0,1); 5.}

其余各項詳見源代碼。 這個列表框的作用不僅僅是顯示窗口的狀態,還要在發生勾選改動時即時改變窗口狀態或激發其行為。勾選狀態改變的消息是LBN_SELCHANGE。另外,為了不使一個勾選的改變就引起所有列表項都激發一遍,我們采用switch結構,以使哪個列表項被選中就激發哪個列表項。代碼如下:

view source print? 01.voidCPage4::OnSelchangeListstatus() 02.{ 03.????// TODO: Add your control notification handler code here 04.????intn=m_listStatus.GetCurSel(); 05.????switch(n) 06.????{ 07.????case0: 08.????????if(m_listStatus.GetCheck(0)== 1 ) 09.????????????::ShowWindow(g_hWnd, SW_SHOW); 10.????????else 11.????????????::ShowWindow(g_hWnd, SW_HIDE); 12.????????break; 13.????case1: 14.????????if(m_listStatus.GetCheck(1) == 1) 15.????????????::EnableWindow(g_hWnd, TRUE); 16.????????else 17.????????????::EnableWindow(g_hWnd,FALSE); 18.????????break; 19.????case2: 20.????????if(m_listStatus.GetCheck(2) == 1) 21.????????????::SetWindowPos(g_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE); 22.????????else 23.????????????::SetWindowPos(g_hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); 24.????????break; 25.????case3: 26.????????if(m_listStatus.GetCheck(3) == 1) 27.????????????::SendMessage(g_hWnd, EM_SETREADONLY, TRUE, 0); 28.????????else 29.????????????::SendMessage(g_hWnd, EM_SETREADONLY, FALSE, 0); 30.????????break; 31.????case4: 32.????????if(m_listStatus.GetCheck(4) ==1) 33.????????{ 34.????????????::ShowWindow(g_hWnd, SW_MAXIMIZE); 35.????????????m_listStatus.SetCheck(5,0); 36.????????} 37.????????else 38.????????????::ShowWindow (g_hWnd, SW_RESTORE); 39.????????break; 40.????case5: 41.????????if(m_listStatus.GetCheck(5) == 1) 42.????????{ 43.????????????::ShowWindow(g_hWnd, SW_MINIMIZE); 44.????????????m_listStatus.SetCheck(4,0); 45.????????} 46.????????else 47.????????????::ShowWindow(g_hWnd, SW_RESTORE); 48.????????break; 49.????case6: 50.????????if(m_listStatus.GetCheck(6) ==1) 51.????????{ 52.????????????::ShowWindow (g_hWnd, SW_RESTORE); 53.????????????m_listStatus.SetCheck(6,0); 54.????????????m_listStatus.SetCheck(5,0); 55.????????????m_listStatus.SetCheck(4,0); 56.????????} 57.????????break; 58.????case7: 59.????????if(m_listStatus.GetCheck(7) ==1) 60.????????{ 61.????????????::SendMessage (g_hWnd, WM_CLOSE, 0, 0); 62.????????????m_listStatus.SetCheck(7,0); 63.????????} 64.????????break; 65.????case8: 66.????????if(m_listStatus.GetCheck(8) ==1) 67.????????{ 68.????????????::BringWindowToTop(g_hWnd); 69.????????????m_listStatus.SetCheck(8,0); 70.????????} 71.????????break; 72.????default: 73.????; 74.????} 75.}

Spy++打造完畢。回顧其過程,難點不多,細細碎碎問題不少。也難免啊,不僅要形似,咱還要神似。文中一定還有很多地方不夠周全,希望同行朋友們不吝賜教。代碼在Window XP + VC6.0中調試通過。Spy++源碼同時放在這里。歡迎訪問我的個人主頁(阿珊境界)http://www.asanscape.com,歡迎加入我們的VC討論群713035。

正文:

打開VC集成開發環境,建立一個基于對話框的工程。我們把這個工程取名為SpyXX。在窗體中畫上一個圖片框控件(Picture)、一個靜態文本控件(Static)、兩個復選框控件(Check Box)和一個選項卡控件(Tab Control)。界面設計如下圖。

探測器的制作需要兩個圖標文件(.ico)和一個鼠標光標文件(.cur),分別用于正常狀態下的顯示、鼠標拖出時的顯示以及拖出時的鼠標指針;這些資源哪里來啊?Spy++中就有啊,用eXeScope挖一下吧。(我是從其他軟件中挖出來的,名字好像叫超級什么霸,記不太清了,呵呵。)選項卡控件定義5個標簽頁,分別為"常規"、"樣式"、"類"、"窗口"和"消息"。每個標簽頁的內容用一個屬性頁(Property Page)對話框來制作。下面,我們按照順序描述一下開發過程。

一、探測器的制作

探測器用一個圖片框控件來顯示,正常狀態下顯示一幅有靶的圖標。當鼠標在上面按下時,顯示內容立刻換為另一幅無靶的圖標,同時鼠標指針變為靶狀。這樣,就給人一種靶心被拖出去的感覺了。通過上面的敘述,我們了解到圖片框需要響應WM_LBUTTONDOWN消息和WM_LBUTTONUP消息。而圖片框在正常狀態下只響應鼠標單擊消息BN_CLICK。所以,我們要通過子類化來響應上述兩個消息。

把圖片框的ID設為IDC_PIC,并選中其Notify屬性(否則不響應消息)。依次點擊菜單Insert->New Class,Class type選擇MFC Class,類名取為CMyPic,基類為CStatic。添加CSpyXXDlg類的私有成員變量CMyPic m_pic,在對話框的初始化過程中將其與圖片框關聯。代碼如下:

view source print? 1.BOOLCSpyXXDlg::OnInitDialog() 2.{ 3.????CDialog::OnInitDialog(); 4.????m_pic.SubclassDlgItem(IDC_PIC,this); 5.????…… 6.????returnTRUE; 7.}

在CMyPic類中,我們就可以響應鼠標左鍵按下和彈起的消息了。按Ctrl + W打開Class Wizard,選擇Message Maps標簽頁,在Class name下拉列表中選擇CMyPic。從Messages列表中分別增加WM_LBUTTONDOWN和WM_LBUTTONUP消息,并接受其缺省函數名OnLButtonDown和OnLButtonUp。圖標交換和鼠標光標交換的代碼如下:

view source print? 01.voidCMyPic::OnLButtonDown(UINTnFlags, CPoint point)? 02.{ 03.????// TODO: Add your message handler code here and/or call default 04.????SetCapture();   //鼠標捕獲 05.????HCURSORhc = LoadCursor(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDC_CURSOR1));? 06.????//IDC_CURSOR1是靶形光標資源號 07.????::SetCursor(hc); 08.????HICONhicon2 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON2));? 09.????//IDI_ICON2為無靶圖標資源號 10.????this->SetIcon(hicon2); 11.????CStatic::OnLButtonDown(nFlags, point); 12.} 13.voidCMyPic::OnLButtonUp(UINTnFlags, CPoint point)? 14.{ 15.????// TODO: Add your message handler code here and/or call default 16.????ReleaseCapture();//釋放鼠標捕獲 17.????HICONhicon1 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON1));? 18.????//IDI_ICON1是有靶圖標資源號 19.????this->SetIcon(hicon1); 20.????CStatic::OnLButtonUp(nFlags, point); 21.}

探測器外觀制作完成了。可以先運行一下,把鼠標按下后拖動試試。下面來實現其功能:獲取窗口句柄。根據鼠標位置來確定窗口需要用到API函數GetCursorPos和WindowFromPoint。此外,我們還想做到像抓圖程序那樣,鼠標移動到的地方,窗口四周會出現閃爍的矩形。這一點,我們用定時器來實現。定時器設在CSpyXXDlg類中,但要由CMyPic中的OnLButtonUp來啟動。所以,我們定義一個全局變量g_hMe將CSpyXXDlg的實例句柄保存起來。同時,被選取的窗口句柄也涉及到在多個標簽頁中顯示,所以也用全局變量g_hWnd將之保存。其余的用于顯示標簽頁的屬性頁對話框句柄分別用g_hPage0、g_hPage1、g_hPage2、g_hPage3和g_hPage4來保存。啟動定時器的代碼如下:

view source print? 1.FromHandle(g_hMe)->SetTimer(1,600,NULL);

在定時器中,我們要實現桌面范圍內的矩形繪制。代碼如下:

view source print? 01.POINT pnt; 02.RECT rc; 03.HWNDDeskHwnd = ::GetDesktopWindow(); //取得桌面句柄 04.HDCDeskDC = ::GetWindowDC(DeskHwnd); //取得桌面設備場景 05.intoldRop2 = SetROP2(DeskDC, R2_NOTXORPEN); 06.::GetCursorPos(&pnt);//取得鼠標坐標 07.HWNDUnHwnd = ::WindowFromPoint(pnt) ; //取得鼠標指針處窗口句柄 08.g_hWnd=UnHwnd; 09.::GetWindowRect(g_hWnd, &rc);//獲得窗口矩形 10.if( rc.left < 0 ) rc.left = 0; 11.if(rc.top < 0 ) rc.top = 0; 12.HPENnewPen = ::CreatePen(0, 3, 0); //建立新畫筆,載入DeskDC 13.HGDIOBJoldPen = ::SelectObject(DeskDC, newPen); 14.::Rectangle(DeskDC, rc.left, rc.top, rc.right, rc.bottom);//在窗口周圍顯示閃爍矩形 15.Sleep(400);//設置閃爍時間間隔 16.::Rectangle( DeskDC, rc.left, rc.top, rc.right, rc.bottom); 17.::SetROP2(DeskDC, oldRop2); 18.::SelectObject( DeskDC, oldPen); 19.::DeleteObject(newPen); 20.::ReleaseDC( DeskHwnd, DeskDC); 21.DeskDC = NULL;

到此,探測器功能全部完成。

二、兩個復選框

第一個復選框是"總在最上面",代碼如下:

view source print? 1.voidCSpyXXDlg::OnChktop()? 2.{ 3.????intnTop=((CButton*)GetDlgItem(IDC_CHKTOP))->GetCheck(); 4.????if(nTop==1) 5.????????:: SetWindowPos(m_hWnd,HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 6.????else 7.????????::SetWindowPos(m_hWnd,HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); 8.}

第二個復選框是"16進制"。因為其值影響到多個屬性頁對話框的內容,所以,也用一全局變量g_nHex保存之:

view source print? 1.voidCSpyXXDlg::OnChkhex()? 2.{ 3.????g_nHex=((CButton*)GetDlgItem(IDC_CHKHEX))->GetCheck(); 4.}

這里,我們還建立了一個全局函數Display,來輸出16進制和10進制時的句柄值:

view source print? 01.CString Display(intnVal) 02.{ 03.????CString str; 04.????if(g_nHex==1) 05.????{ 06.????????str.Format("%x",nVal); 07.????????str.MakeUpper(); 08.????} 09.????else 10.????????str.Format("%d",nVal); 11.????returnstr; 12.}

三、選項卡控件

選項卡控件中,5個標簽頁對應5個屬性頁對話框,與它們關聯的類分別取名為CPage0、CPage1、CPage2、CPage3、CPage4。在CSpyXXDlg中建立私有成員變量m_page0、m_page1、m_page2、m_page3、m_page4。在其初始化過程中建立這5個屬性頁對話框:

view source print? 01.m_page0.Create(IDD_OLE_PROPPAGE_LARGE,GetDlgItem(IDC_TAB1)); 02.m_page1.Create(IDD_OLE_PROPPAGE_LARGE1,GetDlgItem(IDC_TAB1)); 03.m_page2.Create(IDD_OLE_PROPPAGE_LARGE2,GetDlgItem(IDC_TAB1)); 04.m_page3.Create(IDD_OLE_PROPPAGE_LARGE3,GetDlgItem(IDC_TAB1)); 05.m_page4.Create(IDD_OLE_PROPPAGE_LARGE4,GetDlgItem(IDC_TAB1)); 06.CRect rs; 07.m_tab.GetClientRect(rs); 08.rs.top+=20; 09.rs.bottom-=3; 10.rs.left+=3; 11.rs.right-=3; 12.m_page0.MoveWindow(rs); 13.m_page1.MoveWindow(rs); 14.m_page2.MoveWindow(rs); 15.m_page3.MoveWindow(rs); 16.m_page4.MoveWindow(rs); 17.m_page0.ShowWindow(SW_SHOW); 18.m_tab.SetCurSel(0);

然后在選項卡消息TCN_SELCHANGE響應函數中控制它們的顯示:

view source print? 01.voidCSpyXXDlg::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult) 02.{ 03.????// TODO: Add your control notification handler code here 04.????inti=m_tab.GetCurSel(); 05.????switch(i) 06.????{ 07.????case0: 08.????????m_page0.ShowWindow(SW_SHOW); 09.????????m_page1.ShowWindow(SW_HIDE); 10.????????m_page2.ShowWindow(SW_HIDE); 11.????????m_page3.ShowWindow(SW_HIDE); 12.????????m_page4.ShowWindow(SW_HIDE); 13.????????break; 14.????case1: 15.????????m_page0.ShowWindow(SW_HIDE); 16.????????m_page1.ShowWindow(SW_SHOW); 17.????????m_page2.ShowWindow(SW_HIDE); 18.????????m_page3.ShowWindow(SW_HIDE); 19.????????m_page4.ShowWindow(SW_HIDE); 20.????????break; 21.????case2: 22.????????…… 23.????default: 24.????????; 25.????} 26.????*pResult = 0; 27.}

四、常規標簽頁

常規標簽頁負責顯示窗口句柄、窗口類名、標題文本、窗口矩形、窗口ID、進程ID和程序路徑。控制其顯示或改變應在CMyPic的WM_LBUTTONUP響應函函數中進行。代碼如下:

view source print? 01.((CPage0*)FromHandle(g_hPage0))->m_editHWND.SetWindowText(Display((int)g_hWnd)); 02.charstrClass[200]="\0"; 03.::GetClassName(g_hWnd,strClass,200); 04.((CPage0*)FromHandle(g_hPage0))->m_editCLASS.SetWindowText(strClass); 05.((CPage2*)FromHandle(g_hPage2))->SetDlgItemText(IDC_EDITCLASSNAME,strClass);< 06.?????????? 07.charstrTitle[200]="\0"; 08.?::GetWindowText(g_hWnd,strTitle,200); 09.((CPage0*)FromHandle(g_hPage0))->m_editTITLE.SetWindowText (strTitle); 10.longiWNDID=GetWindowLong(g_hWnd,GWL_ID); 11.((CPage0*)FromHandle(g_hPage0))->m_editWNDID.SetWindowText(Display((int)iWNDID)); 12.????????? 13.unsignedlong iPID=0; 14.GetWindowThreadProcessId(g_hWnd,&iPID); 15.((CPage0*)FromHandle(g_hPage0))->m_editPID.SetWindowText(Display((int)iPID)); 16.?????????? 17.CString strPath; 18.strPath=getProcPath(iPID); 19.((CPage0*)FromHandle(g_hPage0))->m_editPATH.SetWindowText(strPath); 20.?????????? 21.RECT rc; 22.::GetWindowRect(g_hWnd, &rc);//獲得窗口矩形 23.CString strRect; 24.strRect.Format("(%d,%d),(%d,%d) %dx%d",rc.left,rc.top,rc.right,rc.bottom, 25.rc.right-rc.left,rc.bottom-rc.top); 26.((CPage0*)FromHandle(g_hPage0))->m_editRECT.SetWindowText(strRect);

其中,getProcPath是獲取進程文件路徑的函數。獲取進程路徑的方法有兩種。在NT系統中,我們可以用OpenProcess()函數將進程打開后,再利用EnumProcessModules()函數枚舉該進程的模塊,最后利用GetModuleFileNameEx()函數就能取得該進程的路徑;第二種方法是利用ToolHelp API中的相關函數。而后者兼容容Windows9x和NT4.0以后系統,所以采取此法。它的實現代碼如下:

view source print? 01.CString getProcPath(intPID) 02.{ 03.????HANDLEhModule; 04.????MODULEENTRY32* minfo=newMODULEENTRY32; 05.????minfo->dwSize=sizeof(MODULEENTRY32); 06.????hModule=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,PID); Module32First(hModule,minfo); 07.????CString str; 08.????str.Format("%s",minfo->szExePath); 09.????CloseHandle(hModule); 10.????if(minfo)delete minfo; 11.????returnstr;? 12.}

五、樣式標簽頁

樣式標簽頁設計如下圖:

API函數GetWindowLong可以獲取窗口樣式或擴展樣式的值。然后我們羅列出以WS_開頭的所有窗口樣式與上述樣式值做"位與"操作,如果被包含,則返回其窗口樣式,否則返回0。這樣,就可以得到窗口樣式的列表了。擴展樣式列表與樣式列表類似。相關代碼如下:

view source print? 01.CListBox* pListStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_STYLE)); 02.CListBox* pListExStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_EX_STYLE)); 03.CEdit* pEditStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_STYLE)); 04.CEdit* pEditExStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_EX_STYLE)); 05.longstyle = GetWindowLong(g_hWnd, GWL_STYLE); 06.longstyleEx= GetWindowLong(g_hWnd, GWL_EXSTYLE); 07.pEditStyle->SetWindowText(Display((int)style)); 08.pEditExStyle->SetWindowText(Display((int)styleEx)); 09.pListStyle->ResetContent();//清空樣式列表框 10.pListExStyle->ResetContent();//清空擴展樣式列表框 11.if(style & WS_BORDER) 12.????pListStyle->AddString("WS_BORDER"); 13.if( style & WS_CAPTION) 14.????pListStyle->AddString("WS_CAPTION"); 15.if( style & WS_CHILD) 16.????pListStyle->AddString("WS_CHILD"); 17.????……

六、類標簽頁

類標簽頁的設計如下圖:

類名在常規標簽頁已獲取。API函數GetClassLong可以獲取類樣式值。樣式列表的實現與窗口樣式類似,不再贅述。

七、窗口標簽頁

窗口標簽頁的設計如下圖:

在該頁中,主要用到了下面幾個API函數:GetNextWindow、GetWindow和SendMessage。這三個API函數搭配以不同的參數值可以實現不同的功能。這里沒有用GetWIndowText函數,是因為它不能取出部分系統窗口和隱藏窗口的標題。我們用SendMessage函數加WM_GETTEXT參數取代之。代碼如下:

view source print? 01.CPage3* pPage3=(CPage3*)FromHandle(g_hPage3); 02.HWNDtempHandle; 03.chartempstr[255]="\0"; 04.tempHandle = g_hWnd;//本窗口句柄 05.pPage3->SetDlgItemText(IDC_MYHWND, Display((int)tempHandle)); 06.//獲取本窗口標題 07.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 08.pPage3->SetDlgItemText(IDC_MYTITLE, tempstr); 09.//上一窗口 10.tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDPREV);? 11.pPage3->SetDlgItemText(IDC_PREHWND, Display((int)tempHandle)); 12.//獲取上一窗口標題 13.memset(tempstr,0,255); 14.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 15.pPage3->SetDlgItemText(IDC_PRETITLE, tempstr); 16.//下一窗口 17.tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDNEXT);? 18.pPage3->SetDlgItemText(IDC_NEXTHWND,Display((int)tempHandle)); 19.memset(tempstr,0,255);//獲取下一窗口標題 20.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 21.pPage3->SetDlgItemText(IDC_NEXTTITLE, tempstr); 22.?????????? 23.tempHandle = ::GetParent(g_hWnd);//父窗口 24.pPage3->SetDlgItemText(IDC_PARENTHWND, Display((int)tempHandle)); 25.memset(tempstr,0,255); 26.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 27.pPage3->SetDlgItemText(IDC_PARENTTITLE,tempstr); 28.//第一子窗口 29.tempHandle = ::GetWindow(g_hWnd, GW_CHILD);? 30.pPage3->SetDlgItemText(IDC_CHILDHWND,Display((int)tempHandle)); 31.memset(tempstr,-0,255); 32.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 33.pPage3->SetDlgItemText(IDC_CHILDTITLE,tempstr); 34.//所有者窗口 35.tempHandle = ::GetWindow(g_hWnd, GW_OWNER);? 36.Page3->SetDlgItemText(IDC_OWNERHWND,Display((int)tempHandle)); 37.memset(tempstr,0,255); 38.::SendMessage(tempHandle, WM_GETTEXT, 255, (LPARAM)tempstr); 39.pPage3->SetDlgItemText(IDC_OWNERTITLE, tempstr);

八、消息標簽頁  消息標簽頁的設計如下圖:

該頁中的列表框與樣式列表框不同,它的每個列表項前都有一個復選框。這要用到類CCheckListBox。這里要再次用到子類化的知識。從本文第一段制作CMyPric過程中,我們體會到了子類化的作用,也感到了它的不便之處。這里,我們采取另外一種方法,借雞生蛋:即用Class Wizard生成相關代碼,然后再修改它。首先在該屬性頁對話框上畫一個列表控件,打開Class Wizard關聯一個CListBox類變量m_listStatus。設置列表框的Owner Draw屬性為Fixed,并選中其Has Strings選項。如下圖:

    

然后,在Page4.h中查找到m_listStatus的定義 CListBox m_listStatus并將其改為CCheckListBox m_listStatus。這樣,我們就可以使用CCheckListBox的全部函數了。

在對話框初始化過程中添加下列語句以加入各列表項:

view source print? 01.CCheckListBox* plistStatus=((CCheckListBox*)FromHandle(g_hPage4)->GetDlgItem(IDC_LISTSTATUS)); 02.plistStatus->AddString("窗口可見"); 03.plistStatus->AddString("窗口可用"); 04.plistStatus->AddString("總在最前"); 05.plistStatus->AddString("窗口只讀"); 06.plistStatus->AddString("最大化"); 07.plistStatus->AddString("最小化"); 08.plistStatus->AddString("窗口還原"); 09.plistStatus->AddString("關閉窗口"); 10.plistStatus->AddString("激活窗口");

接下來我們要判斷,當窗口/控件被選定后,哪些列表項被勾選。這個判斷過程與樣式列表的實現類似。如第一項"窗口可見",代碼如下:

view source print? 1.longstyle = GetWindowLong(g_hWnd, GWL_STYLE); 2.if( style & WS_VISIBLE ) 3.{ 4.????pListStatus->SetCheck(0,1); 5.}

其余各項詳見源代碼。 這個列表框的作用不僅僅是顯示窗口的狀態,還要在發生勾選改動時即時改變窗口狀態或激發其行為。勾選狀態改變的消息是LBN_SELCHANGE。另外,為了不使一個勾選的改變就引起所有列表項都激發一遍,我們采用switch結構,以使哪個列表項被選中就激發哪個列表項。代碼如下:

view source print? 01.voidCPage4::OnSelchangeListstatus() 02.{ 03.????// TODO: Add your control notification handler code here 04.????intn=m_listStatus.GetCurSel(); 05.????switch(n) 06.????{ 07.????case0: 08.????????if(m_listStatus.GetCheck(0)== 1 ) 09.????????????::ShowWindow(g_hWnd, SW_SHOW); 10.????????else 11.????????????::ShowWindow(g_hWnd, SW_HIDE); 12.????????break; 13.????case1: 14.????????if(m_listStatus.GetCheck(1) == 1) 15.????????????::EnableWindow(g_hWnd, TRUE); 16.????????else 17.????????????::EnableWindow(g_hWnd,FALSE); 18.????????break; 19.????case2: 20.????????if(m_listStatus.GetCheck(2) == 1) 21.????????????::SetWindowPos(g_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE); 22.????????else 23.????????????::SetWindowPos(g_hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); 24.????????break; 25.????case3: 26.????????if(m_listStatus.GetCheck(3) == 1) 27.????????????::SendMessage(g_hWnd, EM_SETREADONLY, TRUE, 0); 28.????????else 29.????????????::SendMessage(g_hWnd, EM_SETREADONLY, FALSE, 0); 30.????????break; 31.????case4: 32.????????if(m_listStatus.GetCheck(4) ==1) 33.????????{ 34.????????????::ShowWindow(g_hWnd, SW_MAXIMIZE); 35.????????????m_listStatus.SetCheck(5,0); 36.????????} 37.????????else 38.????????????::ShowWindow (g_hWnd, SW_RESTORE); 39.????????break; 40.????case5: 41.????????if(m_listStatus.GetCheck(5) == 1) 42.????????{ 43.????????????::ShowWindow(g_hWnd, SW_MINIMIZE); 44.????????????m_listStatus.SetCheck(4,0); 45.????????} 46.????????else 47.????????????::ShowWindow(g_hWnd, SW_RESTORE); 48.????????break; 49.????case6: 50.????????if(m_listStatus.GetCheck(6) ==1) 51.????????{ 52.????????????::ShowWindow (g_hWnd, SW_RESTORE); 53.????????????m_listStatus.SetCheck(6,0); 54.????????????m_listStatus.SetCheck(5,0); 55.????????????m_listStatus.SetCheck(4,0); 56.????????} 57.????????break; 58.????case7: 59.????????if(m_listStatus.GetCheck(7) ==1) 60.????????{ 61.????????????::SendMessage (g_hWnd, WM_CLOSE, 0, 0); 62.????????????m_listStatus.SetCheck(7,0); 63.????????} 64.????????break; 65.????case8: 66.????????if(m_listStatus.GetCheck(8) ==1) 67.????????{ 68.????????????::BringWindowToTop(g_hWnd); 69.????????????m_listStatus.SetCheck(8,0); 70.????????} 71.????????break; 72.????default: 73.????; 74.????} 75.}

Spy++打造完畢。回顧其過程,難點不多,細細碎碎問題不少。也難免啊,不僅要形似,咱還要神似。文中一定還有很多地方不夠周全,希望同行朋友們不吝賜教。代碼在Window XP + VC6.0中調試通過。Spy++源碼同時放在這里。歡迎訪問我的個人主頁(阿珊境界)http://www.asanscape.com,歡迎加入我們的VC討論群713035。

總結

以上是生活随笔為你收集整理的Spy++原理初探的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。