Windows 8.1之后,winows系统引入一种叫Connected Standby power model。其目的在于模拟手机关屏后能够省电,而开屏时又能快速响应的特点。在windows 10中,又扩展了这种模式。有了conneted standby mode 和DisConnected standby mode.(https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby)
最主要的意思是睡眠的时候仍然联网,比如:收邮件。进入CS 之后后台仍然能够收邮件,conneted standby 最主要的意思是睡眠的时候仍然联网,比如:收邮件。进入CS 之后后台仍然能够收邮件,唤醒之后马上就能看到新邮件。还有一种是 Disconnected Standby,就是睡眠的时候断网(Modern Standby简介 - WWW.LAB-Z.COM (lab-z.com))。
听起来这都是windows的改善行为。但是对于传统的windows api,或者从windows 7迁移过来的app,这是有影响。很多程序在用户进入sleep的时候,会监听 SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged 做一些保存的动作。但是在windows 10中,如果电源模式为Modern standby 的话,那么这个事件失效的。
原来windows OS 引入 Desktop Activity Moderator PowerRegisterSuspendResumeNotification 这个API来处理这个问题。
https://docs.microsoft.com/en-us/windows/win32/w8cookbook/desktop-activity-moderator 来处理这个问题。
以下演示在代码展示c#程序中如何使用这种方法。
using System;
using System.Runtime.InteropServices;
namespace PowerStatusChanged
{
class Program
{
static void Main(string[] args)
{
IntPtr registrationHandle = new IntPtr();
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS();
recipient.Callback = new DeviceNotifyCallbackRoutine(DeviceNotifyCallback);
recipient.Context = IntPtr.Zero;
IntPtr pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf(recipient));
Marshal.StructureToPtr(recipient, pRecipient, false);
uint result = PowerRegisterSuspendResumeNotification(DEVICE_NOTIFY_CALLBACK, ref recipient, ref registrationHandle);
if (result != 0)
Console.WriteLine("Error registering for power notifications: " + Marshal.GetLastWin32Error());
else
Console.WriteLine("Successfully Registered for power notifications!");
Console.ReadKey();
}
private static int DeviceNotifyCallback(IntPtr context, int type, IntPtr setting)
{
Console.WriteLine("Device notify callback called: ");
switch (type)
{
case PBT_APMPOWERSTATUSCHANGE:
Console.WriteLine("\tPower status has changed.");
break;
case PBT_APMRESUMEAUTOMATIC:
Console.WriteLine("\tOperation is resuming automatically from a low-power state.This message is sent every time the system resumes.");
break;
case PBT_APMRESUMESUSPEND:
Console.WriteLine("\tOperation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.");
break;
case PBT_APMSUSPEND:
Console.WriteLine("\tSystem is suspending operation.");
break;
case PBT_POWERSETTINGCHANGE:
Console.WriteLine("\tA power setting change event has been received. ");
break;
default:
Console.WriteLine("unknown");
break;
}
// do something here
return 0;
}
private const int WM_POWERBROADCAST = 536; // (0x218)
private const int PBT_APMPOWERSTATUSCHANGE = 10; // (0xA) - Power status has changed.
private const int PBT_APMRESUMEAUTOMATIC = 18; // (0x12) - Operation is resuming automatically from a low-power state.This message is sent every time the system resumes.
private const int PBT_APMRESUMESUSPEND = 7; // (0x7) - Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
private const int PBT_APMSUSPEND = 4; // (0x4) - System is suspending operation.
private const int PBT_POWERSETTINGCHANGE = 32787; // (0x8013) - A power setting change event has been received.
private const int DEVICE_NOTIFY_CALLBACK = 2;
/// <summary>
/// OS callback delegate definition
/// </summary>
/// <param name="context">The context for the callback</param>
/// <param name="type">The type of the callback...for power notifcation it's a PBT_ message</param>
/// <param name="setting">A structure related to the notification, depends on type parameter</param>
/// <returns></returns>
public delegate int DeviceNotifyCallbackRoutine(IntPtr context, int type, IntPtr setting);
/// <summary>
/// A callback definition
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
public DeviceNotifyCallbackRoutine Callback;
public IntPtr Context;
}
[DllImport("Powrprof.dll", SetLastError = true)]
static extern uint PowerRegisterSuspendResumeNotification(uint flags, ref DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS receipient, ref IntPtr registrationHandle);
}
}
源码下载
运行以上程序,点击开始-->休眠,唤醒回来后,可以看到以下结果:
以上代码方案参考
ModernStandby下获取系统电源消息_wayyt
pinvoke.net: PowerRegisterSuspendResumeNotification (powrprof)
值得分享的是:
1)以上程序在传统的S3, S4电源模式下,也能响应系统Sleep事件。当将这代码移到GUI程序(如wpf winform等程序时,需要将 registrationHandle,recipient 和 pRecipient 设置为 static 的成员,否则因为生命周期的问题,回调会找不到部分数据,而导致程序在OS resume 回来时挂掉)
2)这里想强调的时,虽然这里监听的是PBT_APMSUSPEND事件,但是千万不要在桌面程序的窗口事件WndindowProc来监听,尽管有这样一套接口官网有这样一套接口
https://docs.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast#:~:text=WM_POWERBROADCAST%20messages%20do%20not%20distinguish%20between%20different%20low-power,state%20transitions%20in%20the%20Windows%20System%20event%20log.
但是我测下来,只能收到插播电源的事件。从以上链接看不出来为什么不行。
一点扩展:
如何查看系统的电源模式呢,在cmd窗口中,使用powercfg -a 可以查看。
比如以下模式,就是一个传统的电源模式, S3 (Sleep), S4(Hibernate).
而以下模式
S0 Low Power Idle表示 Modern standby 模式。
至于什么是Modern standby 模式以及它跟传统的 S3(Sleep), S4(Hibernate)有的区别可以查看
https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby-vs-s3。