在 Windows 中挂接系统电源按钮

在 Windows 中挂接系统电源按钮

我有一台无头计算机,它运行着一项自定义服务,我想使用电源按钮来启用/禁用它,而不必每次都远程连接。这台计算机还会执行其他操作,因此无法将其关闭。

是否可以在 Windows XP 及更高版本下挂接系统电源按钮,以便我的程序能够获取事件Windows 启动关机/睡眠事件(在PBT_APMQUERYSUSPEND发送之前)?

我已经弄清楚了,请参阅下面我自己的答案!

答案1

这确实是可行的,但有点不切实际,而且根据 Windows 版本,需要两种完全不同的实现方式。对于这两种方法,您都需要在电源选项中将电源按钮设置为使计算机进入睡眠状态。

Windows XP 及更低版本:

您需要覆盖程序主窗口的WndProc函数。在不支持此功能的 IDE 上,可以使用SetWindowLonguser32 API 完成此操作。在您的自定义WndProc函数中,监听WM_POWERBROADCAST (0x218)消息。如果您收到 wParam 为 的消息PBT_APMQUERYSUSPEND (0x0),请调用您想要的函数然后返回,BROADCAST_QUERY_DENY (0x424D5144)而不是调用基本WndProc函数。示例代码:

//At program start
//GWL_WNDPROC = -4
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_APMQUERYSUSPEND = 0x0
//BROADCAST_QUERY_DENY = 0x424D5144
if wMsg = WM_POWERBROADCAST && wParam = PBT_APMQUERYSUSPEND (
    //CALL YOUR FUNCTION HERE!
    return BROADCAST_QUERY_DENY
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)

Windows Vista 及更高版本: (感谢 Remy Lebeau 为我指明了正确的方向)

您需要WndProc像 XP 那样进行覆盖,但也要调用SetThreadExecutionStatekernel32 API 来禁用睡眠模式,并RegisterPowerSettingNotification调用 user32 API 来监听高级电源通知。您将特别监听GUID_SYSTEM_AWAYMODE通知,当系统被要求进入睡眠状态但无法进入时,会发出该通知。要轻松地将字符串转换为正确格式,LPCGUID您可以使用UuidFromStringArpcrt4.dll API。示例代码:

typedef struct UUID{
    int d1, d2, d3, d4
} LPCGUID;

//At program start
//GWL_WNDPROC = -4
//ES_CONTINUOUS = 0x80000000
//ES_SYSTEM_REQUIRED = 0x1
//ES_AWAYMODE_REQUIRED = 0x40
//GUID_SYSTEM_AWAYMODE = "98a7f580-01f7-48aa-9c0f-44352c29e5C0"
LPCGUID uid;
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED)
UuidFromStringA(*(GUID_SYSTEM_AWAYMODE), uid)
ps = RegisterPowerSettingNotification(this.hWnd, uid, 0)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_POWERSETTINGCHANGE = 0x8013
if wMsg = WM_POWERBROADCAST && wParam = PBT_POWERSETTINGCHANGE (
    //CALL YOUR FUNCTION HERE!
    //You can additionally extract data from the lParam to verify
    //this is the notification you're waiting for (see below)
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)
UnregisterPowerSettingNotification(ps)

此方法的副作用是关闭您的物理屏幕(在无头机器上不是问题),并且还可能锁定您的会话。请确保在睡眠后禁用提示输入密码以避免这种情况。有关RegisterPowerSettingNotification可用的其他有用信息这里它显示了如何在您需要通知的其他信息时从函数lParam中提取信息。玩得开心 ;)WndProc

答案2

您可以轮询 WMI 以查找关机事件,但无法停止它。因此无法保证您的程序在任何服务终止之前收到该事件。

相关内容