You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无窗体无控制台应用捕获Windows关机消息的实现方法

解决Delphi无窗体循环应用捕获关机消息的问题

嘿,刚好之前折腾过类似需求!你的问题核心在于无窗体程序默认没有窗口来接收Windows系统消息——WM_QUERYENDSESSION和WM_ENDSESSION这类系统消息都是发给窗口的,所以用SetWindowsHookEx完全找错方向了,钩子主要用来监控其他进程或全局消息,对自己的无窗体程序根本没用。

下面给你两种可行的实现方案,先看最贴合你原型的隐藏窗口方法:

方法一:创建隐藏窗口处理系统消息

这是最标准的做法,通过创建一个不可见的窗口作为消息载体,让它接收关机相关的系统消息,再通知你的循环逻辑正常退出。

修改后的完整代码如下:

program SilentApp;
{$APPTYPE GUI}
{$R *.res}

uses
  System.SysUtils, Messages, WinTypes, Windows;

var
  hWndMain: HWND;
  bRunning: Boolean;
  LastCheckTime: DWORD;

// 自定义窗口过程,处理系统消息
function WndProc(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  Result := 0;
  case Msg of
    WM_QUERYENDSESSION:
      begin
        // 这里可以执行关机前的检查逻辑,返回TRUE表示允许系统关机
        Result := 1;
      end;
    WM_ENDSESSION:
      begin
        // 收到关机通知,设置运行标志为False,让循环退出
        bRunning := False;
        // 如果wParam为TRUE,系统马上要强制关机,这里要快速清理资源
        if wParam = TRUE then
        begin
          OutputDebugString('系统即将关机,正在紧急清理...');
        end;
        Result := 0;
      end;
    WM_DESTROY:
      begin
        PostQuitMessage(0);
        Result := 0;
      end;
    else
      Result := DefWindowProc(hWnd, Msg, wParam, lParam);
  end;
end;

// 注册并创建隐藏窗口
procedure CreateHiddenWindow;
var
  WndClass: TWndClass;
begin
  FillChar(WndClass, SizeOf(TWndClass), 0);
  WndClass.style := CS_HREDRAW or CS_VREDRAW;
  WndClass.lpfnWndProc := @WndProc;
  WndClass.hInstance := HInstance;
  WndClass.lpszClassName := 'SilentAppHiddenWindow';
  // 注册窗口类
  if not RegisterClass(WndClass) then
    RaiseLastOSError;
  // 创建隐藏窗口
  hWndMain := CreateWindowEx(
    0,
    'SilentAppHiddenWindow',
    '',
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    0, 0, HInstance, nil
  );
  if hWndMain = 0 then
    RaiseLastOSError;
  // 隐藏窗口,用户完全看不到
  ShowWindow(hWndMain, SW_HIDE);
end;

begin
  try
    CreateHiddenWindow;
    bRunning := True;
    LastCheckTime := GetTickCount;

    // 消息循环 + 业务逻辑循环结合
    while bRunning do
    begin
      // 优先处理Windows消息,确保能及时收到关机通知
      while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
      begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
        if not bRunning then Break; // 收到关机消息就跳出消息循环
      end;

      // 每隔1秒执行业务检查与处理(和你原来的Sleep(1000)逻辑一致)
      if GetTickCount - LastCheckTime >= 1000 then
      begin
        LastCheckTime := GetTickCount;
        // --- 这里写你的业务检查与处理代码 ---
        OutputDebugString('执行业务逻辑...');
      end;

      // 让出CPU时间,避免程序占用过高资源
      Sleep(10);
    end;

    // 程序退出前的清理逻辑
    OutputDebugString('应用正常退出');
    DestroyWindow(hWndMain);
    UnregisterClass('SilentAppHiddenWindow', HInstance);
  except
    on E: Exception do
    begin
      OutputDebugString(PChar('错误:' + E.Message));
    end;
  end;
end.

代码关键说明:

  • 隐藏窗口:这是能收到系统消息的核心前提,没有窗口就接收不到WM_QUERYENDSESSION这类消息;
  • 窗口过程WndProc:专门处理关机相关的两个关键消息,控制循环退出并执行清理;
  • 消息循环:用PeekMessage代替死循环+Sleep,既能响应系统消息,又不耽误业务逻辑执行;
  • 业务触发:用GetTickCount控制每秒执行一次业务,避免Sleep阻塞消息接收。

方法二:使用SetConsoleCtrlHandler(适合控制台类型应用)

如果你愿意把应用改成控制台类型(把{$APPTYPE GUI}改成{$APPTYPE CONSOLE}),可以用SetConsoleCtrlHandler直接捕获关机、注销等事件,不需要创建窗口:

program ConsoleApp;
{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, Windows;

var
  bRunning: Boolean;

function ConsoleHandler(dwCtrlType: DWORD): BOOL; stdcall;
begin
  Result := True;
  case dwCtrlType of
    CTRL_SHUTDOWN_EVENT, CTRL_LOGOFF_EVENT:
      begin
        bRunning := False;
        OutputDebugString('收到关机/注销通知,准备退出');
      end;
    else
      Result := False; // 让系统处理其他未定义的事件
  end;
end;

begin
  try
    if not SetConsoleCtrlHandler(@ConsoleHandler, True) then
      RaiseLastOSError;

    bRunning := True;
    while bRunning do
    begin
      // --- 业务检查与处理 ---
      OutputDebugString('执行业务逻辑...');
      Sleep(1000);
    end;

    OutputDebugString('应用正常退出');
  except
    on E: Exception do
      Writeln('错误:' + E.Message);
  end;
end.

不过这个方法只适用于控制台应用,如果你必须保持GUI无窗体的话,还是第一种方法更合适。

为什么SetWindowsHookEx没用?

SetWindowsHookEx是用来安装全局钩子或线程钩子,监控其他进程的消息,你的无窗体程序本身没有窗口,也没有消息循环,钩子根本没法捕获到发给你的系统消息,自然没用啦。

内容的提问来源于stack exchange,提问作者Supply

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:32:07