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

WinAPI Shell 鉤子回調函數 ShellProc 完整指南:監控視窗、任務列、系統事件

     編輯:Delphi

ShellProc 回調函數概述

ShellProc 是 Windows Shell 鉤子 (WH_SHELL) 的回調函數。它允許應用程式接收 Shell 相關的重要事件通知,如視窗建立/銷毀、啟用/停用、任務列按鈕變更、系統語言切換、螢幕保護程式啟動/停止、閃爍視窗等。這是開發任務管理器、視窗管理工具、自動化測試框架、無障礙輔助技術的核心 API。

函數原型

LRESULT CALLBACK ShellProc(
  _In_ int    nCode,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

核心參數:nCode 事件代碼表

nCode 決定事件類型,wParam 通常為視窗控制代碼 (HWND),lParam 視事件而定。

nCode 值常數名稱說明wParamlParam
1HSHELL_WINDOWCREATED頂層視窗被建立HWND (新視窗)0
2HSHELL_WINDOWDESTROYED頂層視窗被銷毀HWND (舊視窗)0
3HSHELL_ACTIVATESHELLWINDOWShell 視窗被啟用 (如點擊任務列按鈕)HWND0
4HSHELL_WINDOWACTIVATED頂層視窗被啟用 (獲得焦點)HWND (啟用視窗)0
5HSHELL_GETMINRECT請求最小化視窗矩形 (動畫用)HWNDPRECT (輸出矩形)
6HSHELL_REDRAW標題列/任務列按鈕需重繪HWND0
7HSHELL_TASKMAN啟動任務管理器 (Ctrl+Shift+Esc)00
8HSHELL_LANGUAGE系統輸入語言變更HKL (新鍵盤佈局)0
9HSHELL_SYSMENU系統選單啟動 (Alt+Space 或右鍵標題列)HWND0
10HSHELL_ENDTASK請求結束應用程式 (任務列右鍵關閉)HWND0
11HSHELL_ACCESSIBILITYSTATE無障礙狀態變更狀態旗標0
12HSHELL_APPCOMMAND應用程式命令 (多媒體鍵、瀏覽器鍵)HWND命令資訊 (GET_APPCOMMAND_LPARAM)
13HSHELL_WINDOWREPLACED視窗被替換 (如 UAC 同意視窗替換原視窗)HWND (新視窗)HWND (舊視窗)
14HSHELL_WINDOWREPLACING視窗即將被替換HWND (新視窗)HWND (舊視窗)
15HSHELL_HIGHBIT高位標誌 (配合其他代碼表示特殊狀態)--
16HSHELL_FLASH視窗閃爍通知 (FlashWindowEx)HWND0
17HSHELL_RUDEAPPACTIVATED粗魯應用啟用 (全螢幕獨佔模式啟動)HWND0
18HSHELL_RUDEAPPSHELLWINDOW粗魯 Shell 視窗啟用HWND0

返回值規則

  • 大多數情況:返回 0 (系統忽略返回值)
  • HSHELL_GETMINRECT:在 lParam 指向的 RECT 填入最小化動畫目標矩形,返回 TRUE
  • HSHELL_ENDTASK:返回 TRUE 表示允許結束,FALSE 表示攔截 (但系統最終仍可強制結束)

WH_SHELL 安裝關鍵點

// WH_SHELL 必須是全域鉤子,且需要 DLL 注入
// 但有一個特例:使用 SetWindowsHookEx 時 hMod 參數可為 NULL
// 系統會自動將鉤子注入所有符合條件的進程

hHook := SetWindowsHookEx(WH_SHELL, @ShellProc, HInstance, 0);
// 第4參數 0 表示所有執行緒 (全域)
// HInstance 必須是包含 ShellProc 的 DLL 模組句柄
// 若在 EXE 中直接呼叫,需將 ShellProc 放在 DLL 中

完整實戰範例:視窗生命週期監控器 (Delphi DLL + 主程式)

ShellHookDLL.dpr

library ShellHookDLL;

uses
  Windows,
  Messages,
  SysUtils,
  Classes;

{$R *.res}

var
  g_hHook: HHOOK = 0;
  g_MainWnd: HWND = 0;  // 主程式視窗句柄,用於轉發事件

// 導出:設定主程式視窗句柄
procedure SetMainWindow(hWnd: HWND); stdcall;
begin
  g_MainWnd := hWnd;
end;

function ShellProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
  Msg: UINT;
  Wnd: HWND;
  Rect: PRect;
  Cmd: Integer;
begin
  Result := 0;
  
  // 只處理我們關心的事件
  case nCode of
    HSHELL_WINDOWCREATED,
    HSHELL_WINDOWDESTROYED,
    HSHELL_WINDOWACTIVATED,
    HSHELL_WINDOWREPLACED,
    HSHELL_FLASH,
    HSHELL_RUDEAPPACTIVATED,
    HSHELL_APPCOMMAND:
    begin
      Wnd := HWND(wParam);
      // 發送自定義訊息給主程式處理 (避免在鉤子回調中做複雜邏輯)
      if g_MainWnd <> 0 then
      begin
        case nCode of
          HSHELL_WINDOWCREATED:        Msg := WM_USER + 101;
          HSHELL_WINDOWDESTROYED:      Msg := WM_USER + 102;
          HSHELL_WINDOWACTIVATED:      Msg := WM_USER + 103;
          HSHELL_WINDOWREPLACED:       Msg := WM_USER + 104;
          HSHELL_FLASH:                Msg := WM_USER + 105;
          HSHELL_RUDEAPPACTIVATED:     Msg := WM_USER + 106;
          HSHELL_APPCOMMAND:           Msg := WM_USER + 107;
        else Msg := 0;
        end;
        
        if Msg <> 0 then
        begin
          // lParam 傳遞視窗句柄,wParam 傳遞額外資訊
          PostMessage(g_MainWnd, Msg, wParam, LPARAM(Wnd));
        end;
      end;
    end;
    
    HSHELL_GETMINRECT:
    begin
      // 系統請求最小化動畫目標矩形
      Rect := PRect(lParam);
      if (Rect <> nil) and (HWND(wParam) <> 0) then
      begin
        // 返回視窗當前位置作為最小化目標
        GetWindowRect(HWND(wParam), Rect^);
        Result := 1; // 表示已填寫矩形
      end;
    end;
    
    HSHELL_LANGUAGE:
    begin
      if g_MainWnd <> 0 then
        PostMessage(g_MainWnd, WM_USER + 108, wParam, 0);
    end;
  end;
  
  // 必須呼叫 CallNextHookEx 維持鉤子鏈
  Result := CallNextHookEx(g_hHook, nCode, wParam, lParam);
end;

procedure InstallHook; stdcall;
begin
  if g_hHook = 0 then
    g_hHook := SetWindowsHookEx(WH_SHELL, @ShellProc, HInstance, 0);
end;

procedure UninstallHook; stdcall;
begin
  if g_hHook <> 0 then
  begin
    UnhookWindowsHookEx(g_hHook);
    g_hHook := 0;
  end;
end;

exports
  InstallHook,
  UninstallHook,
  SetMainWindow;

begin
  DisableThreadLibraryCalls(HInstance);
end.

主程式 MainUnit.pas

unit MainUnit;

interface

uses
  Windows, Messages, SysUtils, Forms, Controls, StdCtrls, Classes;

type
  TForm1 = class(TForm)
    btnInstall: TButton;
    btnUninstall: TButton;
    memoLog: TMemo;
    chkAutoScroll: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnInstallClick(Sender: TObject);
    procedure btnUninstallClick(Sender: TObject);
  private
    procedure WndProc(var Msg: TMessage); override;
    procedure LogEvent(const Event: string; Wnd: HWND; Extra: LPARAM = 0);
    procedure OnWindowCreated(Wnd: HWND);
    procedure OnWindowDestroyed(Wnd: HWND);
    procedure OnWindowActivated(Wnd: HWND);
    procedure OnWindowReplaced(NewWnd, OldWnd: HWND);
    procedure OnFlash(Wnd: HWND);
    procedure OnRudeAppActivated(Wnd: HWND);
    procedure OnAppCommand(Wnd: HWND; Cmd: Integer);
    procedure OnLanguageChange(KL: HKL);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// 導入 DLL 函數
procedure InstallHook; external 'ShellHookDLL.dll' name 'InstallHook';
procedure UninstallHook; external 'ShellHookDLL.dll' name 'UninstallHook';
procedure SetMainWindow(hWnd: HWND); external 'ShellHookDLL.dll' name 'SetMainWindow';

procedure TForm1.FormCreate(Sender: TObject);
begin
  memoLog.Lines.Add('Shell Hook 監控器就緒');
  chkAutoScroll.Checked := True;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  UninstallHook;
end;

procedure TForm1.btnInstallClick(Sender: TObject);
begin
  InstallHook;
  // 必須告知 DLL 主程式視窗句柄
  SetMainWindow(Handle);
  memoLog.Lines.Add('[Hook Installed] 監控已啟動');
end;

procedure TForm1.btnUninstallClick(Sender: TObject);
begin
  UninstallHook;
  memoLog.Lines.Add('[Hook Uninstalled] 監控已停止');
end;

procedure TForm1.WndProc(var Msg: TMessage);
begin
  case Msg.Msg of
    WM_USER + 101: OnWindowCreated(HWND(Msg.LParam));
    WM_USER + 102: OnWindowDestroyed(HWND(Msg.LParam));
    WM_USER + 103: OnWindowActivated(HWND(Msg.LParam));
    WM_USER + 104: OnWindowReplaced(HWND(Msg.WParam), HWND(Msg.LParam));
    WM_USER + 105: OnFlash(HWND(Msg.LParam));
    WM_USER + 106: OnRudeAppActivated(HWND(Msg.LParam));
    WM_USER + 107: OnAppCommand(HWND(Msg.LParam), GET_APPCOMMAND_LPARAM(Msg.LParam));
    WM_USER + 108: OnLanguageChange(HKL(Msg.WParam));
  end;
  inherited;
end;

procedure TForm1.LogEvent(const Event: string; Wnd: HWND; Extra: LPARAM = 0);
var
  Title: array[0..255] of Char;
  ClassName: array[0..255] of Char;
  ProcessId: DWORD;
begin
  GetWindowText(Wnd, Title, 256);
  GetClassName(Wnd, ClassName, 256);
  GetWindowThreadProcessId(Wnd, @ProcessId);
  
  memoLog.Lines.Add(Format('[%s] %s | HWND:%x | PID:%d | Class:%s | Title:%s',
    [FormatDateTime('hh:nn:ss.zzz', Now), Event, Wnd, ProcessId, ClassName, Title]));
  
  if chkAutoScroll.Checked then
    memoLog.Perform(EM_SCROLLCARET, 0, 0);
    
  // 限制行數
  if memoLog.Lines.Count > 2000 then
    memoLog.Lines.Delete(0);
end;

procedure TForm1.OnWindowCreated(Wnd: HWND);
begin
  LogEvent('WINDOW CREATED', Wnd);
end;

procedure TForm1.OnWindowDestroyed(Wnd: HWND);
begin
  LogEvent('WINDOW DESTROYED', Wnd);
end;

procedure TForm1.OnWindowActivated(Wnd: HWND);
begin
  LogEvent('WINDOW ACTIVATED', Wnd);
end;

procedure TForm1.OnWindowReplaced(NewWnd, OldWnd: HWND);
begin
  LogEvent('WINDOW REPLACED', NewWnd);
  memoLog.Lines.Add(Format('  Old HWND: %x', [OldWnd]));
end;

procedure TForm1.OnFlash(Wnd: HWND)
begin
  LogEvent('WINDOW FLASH', Wnd);
end;

procedure TForm1.OnRudeAppActivated(Wnd: HWND);
begin
  LogEvent('RUDE APP ACTIVATED (Fullscreen)', Wnd);
end;

procedure TForm1.OnAppCommand(Wnd: HWND; Cmd: Integer);
const
  CmdNames: array[0..30] of string = (
    'APPCOMMAND_BROWSER_BACKWARD', 'APPCOMMAND_BROWSER_FORWARD',
    'APPCOMMAND_BROWSER_REFRESH', 'APPCOMMAND_BROWSER_STOP',
    'APPCOMMAND_BROWSER_SEARCH', 'APPCOMMAND_BROWSER_FAVORITES',
    'APPCOMMAND_BROWSER_HOME', 'APPCOMMAND_VOLUME_MUTE',
    'APPCOMMAND_VOLUME_DOWN', 'APPCOMMAND_VOLUME_UP',
    'APPCOMMAND_MEDIA_NEXTTRACK', 'APPCOMMAND_MEDIA_PREVIOUSTRACK',
    'APPCOMMAND_MEDIA_STOP', 'APPCOMMAND_MEDIA_PLAY_PAUSE',
    'APPCOMMAND_LAUNCH_MAIL', 'APPCOMMAND_LAUNCH_MEDIA_SELECT',
    'APPCOMMAND_LAUNCH_APP1', 'APPCOMMAND_LAUNCH_APP2',
    'APPCOMMAND_BASS_DOWN', 'APPCOMMAND_BASS_UP',
    'APPCOMMAND_TREBLE_DOWN', 'APPCOMMAND_TREBLE_UP'
  );
var
  CmdStr: string;
begin
  if (Cmd >= 0) and (Cmd < Length(CmdNames)) then
    CmdStr := CmdNames[Cmd]
  else
    CmdStr := Format('Unknown(%d)', [Cmd]);
    
  LogEvent('APP COMMAND: ' + CmdStr, Wnd);
end;

procedure TForm1.OnLanguageChange(KL: HKL);
var
  LayoutName: array[0..KL_NAMELENGTH-1] of Char;
begin
  GetKeyboardLayoutName(LayoutName);
  LogEvent('LANGUAGE CHANGED', 0);
  memoLog.Lines.Add(Format('  New Layout: %s (HKL:%x)', [LayoutName, KL]));
end;

end.

進階應用場景

1. 任務列按鈕狀態監控

監控 HSHELL_REDRAW + HSHELL_WINDOWACTIVATED 可追蹤任務列按鈕高亮狀態,實現自定義任務切換器。

2. 全螢幕應用偵測 (遊戲/播放器)

// HSHELL_RUDEAPPACTIVATED 表示進入獨佔全螢幕模式
// 可用於:自動隱藏桌面工具列、暫停後台下載、切換效能模式
procedure TForm1.OnRudeAppActivated(Wnd: HWND);
begin
  if IsFullScreenGame(Wnd) then
  begin
    EnableGameMode(True);  // 優化系統資源
    HideDesktopWidgets();
  end;
end;

3. 多媒體鍵全域熱鍵

HSHELL_APPCOMMAND 可捕獲鍵盤上的音量鍵、播放控制鍵、瀏覽器鍵、郵件鍵等,無需註冊熱鍵即可全域響應。

4. 視窗最小化動畫自定義

處理 HSHELL_GETMINRECT 可控制視窗最小化到任務列時的動畫目標位置,實現特效最小化 (如縮放到系統匣圖示)。

注意事項與限制

  1. 必須使用 DLL:WH_SHELL 是全域鉤子,回調必須在 DLL 中。EXE 直接安裝會失敗或僅對自身進程生效。
  2. 位數匹配:32位 DLL 只能鉤住 32位進程,64位同理。需提供兩版 DLL。
  3. UAC 隔離:低完整性進程 (IE Protected Mode、沙箱應用) 的 Shell 事件不會傳遞到高完整性鉤子。
  4. 效能影響:Shell 鉤子會在每個視窗建立/銷毀/啟用時觸發,高頻 UI 操作可能產生大量回調。回調必須極度輕量,建議用 PostMessage 轉發給主執行緒。
  5. 不捕獲子視窗:WH_SHELL 僅通知頂層視窗 (WS_EX_APPWINDOW 或 WS_EX_TOOLWINDOW),不包含子視窗/控制項。
  6. 系統保護視窗:某些系統關鍵視窗 (如 UAC 同意視窗、鎖定畫面) 的事件可能被過濾或延遲。

相關 API 快速查閱

  • SetWindowsHookEx(WH_SHELL, ...) - 安裝 Shell 鉤子
  • CallNextHookEx - 傳遞鉤子鏈
  • UnhookWindowsHookEx - 移除鉤子
  • FlashWindowEx - 觸發 HSHELL_FLASH
  • GET_APPCOMMAND_LPARAM - 從 lParam 提取 AppCommand
  • GetWindowThreadProcessId - 獲取視窗進程 ID
  • GetWindowText / GetClassName - 獲取視窗標題/類名
  • GetKeyboardLayoutName - 獲取當前輸入法名稱

總結

ShellProc 配合 WH_SHELL 是 Windows 系統級視窗事件監控的核心機制。它提供了視窗生命週期、啟用狀態、任務列互動、多媒體鍵、語言切換、全螢幕模式等豐富事件。關鍵實現要點:必須用 DLL、回調極度輕量用 PostMessage 轉發、雙版本 DLL 支援 32/64 位、注意 UAC 隔離限制。掌握此技術可構建強大的視窗管理器、自動化測試框架、無障礙工具、遊戲伴侶等應用。

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