-->

Can't catch sleep/suspend messages (winXP)

2020-02-09 14:49发布

问题:

My app needs to block sleep/hibernate mode. I have the code in place, but after successfully catching the WM_POWERBROADCAST message, neither PBT_APMQUERYSUSPEND nor PBT_APMQUERYSTANDBY are being caught successfully. Interestingly, the PBT_APMRESUMECRITICAL and PBT_APMRESUMEAUTOMATIC messages are being caught by my app.

Bottom line question: is there any reason why my app would fail to catch the standby/suspend messages, but succeed in catching the resume messages?

This Q&A [stackoverflow.com] helped, btw, but again, the messages don't seem to be making it to my app.

My code (w/ event logging code removed for brevity):

        protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        // Power status event triggered
        if (m.Msg == (int)NativeMethods.WindowMessage.WM_POWERBROADCAST)
        {
            // Machine is trying to enter suspended state
            if (m.WParam.ToInt32() == (int)NativeMethods.WindowMessage.PBT_APMQUERYSUSPEND ||
                m.WParam.ToInt32() == (int)NativeMethods.WindowMessage.PBT_APMQUERYSTANDBY)
            {
                // Have perms to deny this message?
                if((m.LParam.ToInt32() & 0x1) != 0)
                {
                    // If so, deny broadcast message
                    m.Result = new IntPtr((int)NativeMethods.WindowMessage.BROADCAST_QUERY_DENY);
                }
            }
            return; // ?!
        }

        base.WndProc(ref m);
    }

回答1:

It works now, for both XP and Vista. I created a stub winform app with the relevant code (could be cleaned up, obviously, but it conveys the point).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace standbyTest
{
    public partial class Form1 : Form
    {

        [DllImport("Kernel32.DLL", CharSet = CharSet.Auto, SetLastError = true)]
        protected static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE state);

        [Flags]
        public enum EXECUTION_STATE : uint
        {
            ES_CONTINUOUS = 0x80000000,
            ES_DISPLAY_REQUIRED = 2,
            ES_SYSTEM_REQUIRED = 1,
            ES_AWAYMODE_REQUIRED = 0x00000040
        }

        public Form1()
        {
            if(Environment.OSVersion.Version.Major > 5)
            {
                // vista and above: block suspend mode
                SetThreadExecutionState(EXECUTION_STATE.ES_AWAYMODE_REQUIRED | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
            }

            InitializeComponent();

            //MessageBox.Show(string.Format("version: {0}", Environment.OSVersion.Version.Major.ToString() ));

        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            if(Environment.OSVersion.Version.Major > 5)
            {
                // Re-allow suspend mode
                SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
            }
        }


        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            // Power status event triggered
            if(m.Msg == (int)WindowMessage.WM_POWERBROADCAST)
            {
                // Machine is trying to enter suspended state
                if(m.WParam.ToInt32() == (int)WindowMessage.PBT_APMQUERYSUSPEND ||
                        m.WParam.ToInt32() == (int)WindowMessage.PBT_APMQUERYSTANDBY)
                {
                    // Have perms to deny this message?
                    if((m.LParam.ToInt32() & 0x1) != 0)
                    {
                        // If so, deny broadcast message
                        m.Result = new IntPtr((int)WindowMessage.BROADCAST_QUERY_DENY);
                    }
                }
                return;
            }

            base.WndProc(ref m);
        }
    }



    internal enum WindowMessage
    {

        /// <summary>
        /// Notify that machine power state is changing
        /// </summary>
        WM_POWERBROADCAST = 0x218,
        /// <summary>
        /// Message indicating that machine is trying to enter suspended state
        /// </summary>
        PBT_APMQUERYSUSPEND = 0x0,
        PBT_APMQUERYSTANDBY = 0x0001,

        /// <summary>
        /// Message to deny broadcast query
        /// </summary>
        BROADCAST_QUERY_DENY = 0x424D5144


    }
}


回答2:

Try to subscribe the PowerModeChanged event:

How do I check when the computer is being put to sleep or wakes up?



回答3:

Are you running on Vista or Windows Server 2008? This page says

Due to changes in the Power Management Model for Windows Vista and Windows Server 2008, the PBT-APMQUERYSUSPEND event is no longer delivered to applications. Instead the BT_APMSUSPEND event is delivered...

Could that be why you're not seeing it?



回答4:

I tried this same code in a test app on my (dev) machine & on a different (test) machine (also winXP). On my machine, it continues to fail, meaning the machine goes into sleep. But on the other machine, it works! At first I thought this was a debug vs release mode issue, but that's not the case.

It seems that something is different about my dev machine, though I have no idea what it could be.

Mystery solved... sorta.



回答5:

In Vista call SetThreadExecutionState to notify WPM that system is not idle.

In Windows XP/2000:
An application can return BROADCAST_QUERY_DENY to deny a PBT_APMQUERYSUSPEND or PBT_APMQUERYSUSPENDFAILED request.

MSDN: Windows XP and earlier: The system broadcasts a PBT_APMQUERYSUSPEND event to request permission to suspend system operation. The system expects each application and driver to determine whether the requested event should occur and to return TRUE if it occurs, or return BROADCAST_QUERY_DENY otherwise. Applications should not deny this request. If an application denies this request, the system broadcasts a PBT_APMQUERYSUSPENDFAILED event. This event notifies applications and drivers to continue operation as usual.

Also I don't think either PBT_APMQUERYSTANDBY or PBT_APMSTANDBY are supported in Win2K. Have you tried gloablly logging broadcast when Windows shuts down to see if they are being sent?