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

《魔高一丈 2.0》開發實戰:全域滑鼠鉤子實現一鍵隱藏/還原所有視窗

     編輯:VC++

專案緣起:老闆鑰匙的終極形態

工作閒暇瀏覽網頁、玩遊戲、寫日記時,老闆/媽媽/另一半突然出現 —— 手忙腳亂逐一點擊最小化按鈕?本專案實現全域滑鼠左鍵+右鍵同時按下 觸發隱藏所有可見視窗,再次觸發還原,並以系統托盤圖示提供介面,徹底解決「社死」危機。

專案示意圖

核心架構:DLL + EXE 分離的全域鉤子模式

Windows 全域鉤子 (WH_MOUSE) 要求鉤子程序位於 DLL 中,以便系統將其映射至所有目標進程位址空間。

專案結構

  • Mousehook.dll (MFC Extension DLL):包含鉤子程序、共享資料段、CMousehook 封裝類。
  • HideWindow.exe (MFC Dialog-based EXE):UI 介面、托盤圖示、載入 DLL 啟動/停止鉤子。

DLL 核心實作

1. 共享資料段:跨進程同步狀態

// Mousehook.cpp
#pragma data_seg("SHARED_DATA") // 共享段名稱
HINSTANCE g_hInst = NULL;       // DLL 實例控制代碼
HHOOK     g_hHook = NULL;       // 鉤子控制代碼
HWND      g_HiddenWnds[100] = {NULL}; // 隱藏視窗控制代碼陣列 (必須初始化!)
UINT      g_HiddenCount = 0;    // 陣列索引
BOOL      g_LeftDown = FALSE;   // 左鍵按下旗標
BOOL      g_RightDown = FALSE;  // 右鍵按下旗標
BOOL      g_HideOrShow = FALSE; // 當前狀態:FALSE=隱藏, TRUE=還原
BOOL      g_Check1 = FALSE, g_Check2 = FALSE, g_Check3 = FALSE; // UI 選項同步
#pragma data_seg()

// 連結器指示:設定共享段屬性為 READ/WRITE/SHARED
// 專案屬性 → 連結器 → 命令列 → /SECTION:SHARED_DATA,RWS
// 或 .def 檔:SECTIONS SHARED_DATA READ WRITE SHARED
關鍵: 變數必須初始化,否則編譯器會放入 .bss 未初始化段,無法共享。

2. 鉤子程序:攔截滑鼠訊息

LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0) return CallNextHookEx(g_hHook, nCode, wParam, lParam);
    
    MOUSEHOOKSTRUCT* pInfo = (MOUSEHOOKSTRUCT*)lParam;
    
    switch (wParam)
    {
        case WM_LBUTTONDOWN: g_LeftDown = TRUE; break;
        case WM_LBUTTONUP:   g_LeftDown = FALSE; break;
        case WM_RBUTTONDOWN: g_RightDown = TRUE; break;
        case WM_RBUTTONUP:   g_RightDown = FALSE; break;
    }
    
    // 偵測「同時按下」:任一按下時檢查另一鍵是否已按下
    if ((wParam == WM_LBUTTONDOWN && g_RightDown) ||
        (wParam == WM_RBUTTONDOWN && g_LeftDown))
    {
        ToggleWindowsVisibility(pInfo->hwnd); // 忽略觸發視窗本身
    }
    
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

void ToggleWindowsVisibility(HWND hTriggerWnd)
{
    if (!g_HideOrShow) // 目前顯示中 → 隱藏
    {
        g_HiddenCount = 0;
        EnumWindows(EnumWindowsProc, 0); // 列舉所有頂層視窗
        g_HideOrShow = TRUE;
    }
    else // 目前隱藏中 → 還原
    {
        for (UINT i = 0; i < g_HiddenCount; ++i)
        {
            if (g_HiddenWnds[i] && IsWindow(g_HiddenWnds[i]))
                ShowWindow(g_HiddenWnds[i], SW_SHOW);
        }
        g_HiddenCount = 0;
        g_HideOrShow = FALSE;
    }
}

3. 列舉視窗回呼:篩選並隱藏

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    // 篩選條件:可見、非觸發視窗、非工作列/托盤/桌面視窗
    if (IsWindowVisible(hwnd) && 
        hwnd != GetShellWindow() && // 排除桌面
        GetWindow(hwnd, GW_OWNER) == NULL && // 僅頂層視窗
        !IsIconic(hwnd)) // 未最小化
    {
        // 可選:排除特定類別名稱 (如 "Shell_TrayWnd", "Button" 等)
        TCHAR className[256];
        GetClassName(hwnd, className, 256);
        if (_tcscmp(className, _T("Shell_TrayWnd")) == 0) return TRUE;
        
        ShowWindow(hwnd, SW_HIDE);
        if (g_HiddenCount < 100)
            g_HiddenWnds[g_HiddenCount++] = hwnd;
    }
    return TRUE; // 繼續列舉
}

4. 封裝類 CMousehook

class AFX_EXT_CLASS CMousehook : public CObject
{
public:
    CMousehook() {}
    ~CMousehook() { StopHook(); }
    
    BOOL StartHook()
    {
        if (g_hHook) return TRUE;
        g_hHook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, g_hInst, 0); // 0 = 全域
        return g_hHook != NULL;
    }
    
    BOOL StopHook()
    {
        if (g_hHook)
        {
            UnhookWindowsHookEx(g_hHook);
            g_hHook = NULL;
            // 確保退出時還原所有視窗
            if (g_HideOrShow) ToggleWindowsVisibility(NULL);
            return TRUE;
        }
        return FALSE;
    }
    
    // UI 同步方法...
    void SetCheck1(UINT i) { g_Check1 = i; }
    void SetCheck2(UINT i) { g_Check2 = i; }
    void SetCheck3(UINT i) { g_Check3 = i; }
    void UseForExit() { if (g_HideOrShow) ToggleWindowsVisibility(NULL); }
};

EXE 端整合

1. 對話方塊初始化:載入 DLL、啟動鉤子、建立托盤圖示

BOOL CHideWindowDlg::OnInitDialog()
{
    COwnerDrawDialog::OnInitDialog();
    
    // 1. 載入 DLL 並啟動鉤子
    m_Hook.StartHook(); // m_Hook 為 CMousehook 成員
    
    // 2. 隱藏工作列顯示,僅保留托盤圖示
    LONG exStyle = GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE);
    SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, exStyle | WS_EX_TOOLWINDOW);
    
    // 3. 建立托盤圖示
    NOTIFYICONDATA nid = {0};
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = GetSafeHwnd();
    nid.uID = IDI_MAINFRAME;
    nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    nid.uCallbackMessage = WM_MY_TRAY_NOTIFY; // 自訂訊息
    nid.hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINFRAME));
    _tcscpy_s(nid.szTip, _T("魔高一丈 2.0 - 雙鍵隱藏視窗"));
    Shell_NotifyIcon(NIM_ADD, &nid);
    
    // 4. 立即隱藏主對話框
    ShowWindow(SW_HIDE);
    
    return TRUE;
}

2. 托盤訊息處理:左鍵顯示設定、右鍵選單

LRESULT CHideWindowDlg::OnTrayNotify(WPARAM wParam, LPARAM lParam)
{
    if (wParam != IDI_MAINFRAME) return 0;
    
    if (lParam == WM_LBUTTONDOWN) // 左鍵單擊:顯示/隱藏設定對話框
    {
        if (IsWindowVisible()) ShowWindow(SW_HIDE);
        else { ShowWindow(SW_SHOW); SetForegroundWindow(); }
    }
    else if (lParam == WM_RBUTTONDOWN) // 右鍵:彈出選單
    {
        CMenu menu; menu.LoadMenu(IDR_TRAY_MENU);
        CMenu* pPopup = menu.GetSubMenu(0);
        CPoint pt; GetCursorPos(&pt);
        SetForegroundWindow(); // 修正選單焦點問題
        pPopup->TrackPopupMenu(TPM_LEFTALIGN, pt.x, pt.y, this);
    }
    return 0;
}

3. 程式結束清理

CHideWindowDlg::~CHideWindowDlg()
{
    // 1. 移除托盤圖示
    NOTIFYICONDATA nid = {0};
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = GetSafeHwnd();
    nid.uID = IDI_MAINFRAME;
    Shell_NotifyIcon(NIM_DELETE, &nid);
    
    // 2. 停止鉤子、還原視窗
    m_Hook.UseForExit(); // 內部呼叫 StopHook()
    m_Hook.StopHook();
}

部署與執行

  1. 編譯 Mousehook.dll (Debug/Release)。
  2. 編譯 HideWindow.exe,連結 Mousehook.lib。
  3. 將 Mousehook.dll 複製至 HideWindow.exe 同目錄。
  4. 執行 HideWindow.exe,托盤區出現五星圖示。
  5. 任意視窗中同時按住滑鼠左右鍵 → 所有視窗隱藏;再次按下 → 還原。

已知限制與改進方向

限制改進建議
固定陣列 g_HiddenWnds[100] 可能溢位改用共享記憶體 mmap + 動態陣列或連結串列
無法隱藏 UWP/Store Apps 視窗需更高權限或 IApplicationView 介面
全域鉤子注入所有進程,效能影響改用 WH_MOUSE_LL 低層鉤子 (不需 DLL,但須在同一執行緒處理)
僅支援 32 位或 64 位單一架構提供 x86/x64 兩版 DLL,EXE 依架構載入
殺毒軟體可能誤報全域鉤子簽名驗證、行為白名單、改用低層鉤子

結語

《魔高一丈 2.0》展示了 Win32 全域鉤子、共享資料段、DLL 注入、托盤程式設計等經典技術的完整串聯。雖然現代 Windows 安全機制 (UIPI, CFG, HVCI) 對全域鉤子限制日益嚴格,但理解其原理對於系統級開發、安全研究、相容舊專案維護仍具核心價值。若重寫於今日,建議採用 WH_MOUSE_LL + 獨立服務進程 + 命名管道通訊 架構,在安全與功能間取得平衡。

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