首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Spy++ 核心原理與自製窗口探測器實戰:從子類化到全域鉤子

     編輯:關於VC++

專案架構:對話框基礎 + 5 頁屬性頁

  • 主對話框:探測器圖標、置頂/16進制選項、Tab Control (常規/樣式/類/窗口/消息)。
  • 5 個 CPropertyPage 對應 5 個標籤頁,動態建立/顯示/隱藏。

核心模組一:探測器圖標 (Finder Tool)

視覺效果實現

  • 靜態圖片控件 CStatic 子類化為 CMyPic。
  • 響應 WM_LBUTTONDOWN:SetCapture()、載入準星游標 (IDC_CURSOR1)、切換無靶圖標 (IDI_ICON2)。
  • 響應 WM_LBUTTONUP:ReleaseCapture()、恢復有靶圖標 (IDI_ICON1)、啟動主對話框定時器 (ID=1, 600ms)。

定時器追蹤邏輯 (主對話框 OnTimer)


void CSpyXXDlg::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == 1) {
        POINT pt; ::GetCursorPos(&pt);
        HWND hWnd = ::WindowFromPoint(pt); // 取得游標下窗口句柄
        g_hWnd = hWnd; // 全域變數共享給各屬性頁

        // 繪製閃爍矩形框 (R2_NOTXORPEN 模式)
        HWND hDesk = ::GetDesktopWindow();
        HDC hDC = ::GetWindowDC(hDesk);
        int oldROP = SetROP2(hDC, R2_NOTXORPEN);
        RECT rc; ::GetWindowRect(hWnd, &rc);
        HPEN hPen = CreatePen(PS_SOLID, 3, RGB(0,0,0));
        HGDIOBJ oldPen = SelectObject(hDC, hPen);
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
        Sleep(100); // 閃爍間隔
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); // 再畫一次抹除
        SelectObject(hDC, oldPen); DeleteObject(hPen);
        SetROP2(hDC, oldROP);
        ::ReleaseDC(hDesk, hDC);
    }
    CDialog::OnTimer(nIDEvent);
}

核心模組二:屬性頁資料填充 (探測器釋放捕獲時觸發)

常規頁 (CPage0):基礎屬性


// 句柄 (16/10 進制切換)
pPage0->m_editHWND.SetWindowText(Display((int)g_hWnd));
// 類名
char szClass[256]; ::GetClassName(g_hWnd, szClass, 256);
pPage0->m_editCLASS.SetWindowText(szClass);
// 標題
char szTitle[256]; ::GetWindowText(g_hWnd, szTitle, 256);
pPage0->m_editTITLE.SetWindowText(szTitle);
// 窗口 ID
pPage0->m_editWNDID.SetWindowText(Display(GetWindowLong(g_hWnd, GWL_ID)));
// 進程 ID + 路徑
DWORD dwPID; GetWindowThreadProcessId(g_hWnd, &dwPID);
pPage0->m_editPID.SetWindowText(Display(dwPID));
pPage0->m_editPATH.SetWindowText(GetProcPath(dwPID)); // ToolHelp32 快照獲取路徑
// 窗口矩形
RECT rc; GetWindowRect(g_hWnd, &rc);
CStr.Format(_T("(%d,%d)-(%d,%d) %dx%d"), rc.left,rc.top,rc.right,rc.bottom, rc.right-rc.left, rc.bottom-rc.top);
pPage0->m_editRECT.SetWindowText(CStr);

樣式頁 (CPage1):位元組解碼

  • GetWindowLong(hWnd, GWL_STYLE) / GWL_EXSTYLE 取得樣式位元組。
  • 遍歷預定義 WS_* / WS_EX_* 常數,(style & WS_BORDER) ? AddString : Skip。
  • 十六進制/十進制切換由全域 g_nHex 控制 Display() 格式化。

類別頁 (CPage2):GetClassLong 同樣列舉 CS_* 樣式。

窗口關係頁 (CPage3):導航窗口樹

關鍵 API 組合:

關係API參數
上一個同級GetNextWindowGW_HWNDPREV
下一個同級GetNextWindowGW_HWNDNEXT
父窗口GetParent-
第一個子窗口GetWindowGW_CHILD
擁有者窗口GetWindowGW_OWNER

標題獲取用 SendMessage(hWnd, WM_GETTEXT, 255, (LPARAM)buf) 替代 GetWindowText,可讀取系統/隱藏窗口標題。

消息頁 (CPage4):CCheckListBox 互動控制

  • 資源編輯器建立 ListBox,設 Owner Draw: Fixed + Has Strings。
  • 代碼中將 CListBox 變數強制轉型 CCheckListBox (繼承關係允許)。
  • 初始化添加 9 項狀態 (可見/啟用/置頂/唯讀/最大化/最小化/還原/關閉/激活)。
  • LBN_SELCHANGE 判斷 GetCheck(index) 執行對應 API:

// 範例:置頂切換
if (GetCheck(2)) SetWindowPos(g_hWnd, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE);
else SetWindowPos(g_hWnd, HWND_NOTOPMOST, 0,0,0,0, SWP_NOMOVE|SWP_NOSIZE);
// 互斥邏輯:最大化/最小化/還原 三選一

關鍵技術總結

  1. 子類化:CMyPic 截獲按鈕非標準訊息;CCheckListBox 變數轉型法避免新建類。
  2. 全域變數共享:g_hWnd, g_hPage0..4, g_nHex 簡化跨模組數據流 (單執行緒 UI 安全)。
  3. 桌面 DC 直接繪圖:GetWindowDC(GetDesktopWindow()) + R2_NOTXORPEN 實現無痕閃爍框。
  4. ToolHelp32 快照:CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PID) 兼容 9x/NT 獲取進程路徑。
  5. WM_GETTEXT 替代 GetWindowText:繞過內部限制,讀取更多窗口標題。

本專案在 WinXP + VC6.0 調試通過。原始碼包含完整資源與類別實現,是學習 Windows 窗口管理、訊息機制、Spy++ 內部原理的極佳教具。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved