Click here to Skip to main content
Click here to Skip to main content

Automated Power Management Operations of Windows (S3/S4) in C# WPF

, 20 Aug 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Programming implementation of Power Management Operations of the Operating System (suspend/hibernate and resume completely) without human interaction.

Introduction

I haven’t seen any completely automated programming implementation of power management operations (sleep and hibernate) of Windows, and that’s why I am writing this. In this article, I will explain and demonstrate how can we do sleep (suspend) or hibernate and then resume operations of Windows using different ways in WPF. My main focus will be on without user interaction. The sample code is tested on Vista and Windows 7 Operating Systems.

A waitable timer is basically a kernel object that is signaled at a certain time or at regular intervals (depends on the parameter of the SetWaitableTimer() function). Asynchronous procedure calls (APC) can be associated with a waitable timer object to allow a callback function to be executed when the timer object is signaled. You can create, execute, and cancel a waitable timer by calling the CreateWaitableTimer(), SetWaitableTimer(), and CancelWaitableTimer() functions of Kernel32.dll.

The CreateWaitableTimer() function returns a handle to the kernel object. We may use the OpenWaitableTimer() function to open an existing named waitable timer object. SetWaitableTimer() will set the timer. In the SetWaitableTimer() function’s fourth parameter, we can associate an asynchronous procedure call (APC) function which will be called as a completion routine. We can also pass some arguments to the completion routine using the fifth parameter of this function. The completion routine will always be executed by the same thread which called the SetWaitableTimer() function. So that thread must put itself in an alertable state. This can be accomplished using many ways. I am using SleepEx() of Kernel32.dll.

For the notification of our system’s power mode change, we can use three different ways. One is to use the PowerModeChanged event of the SystemEvents class which is in the Microsoft.Win32 namespace in System.dll. Second one is by using the window message, and the third one is by adding custom events in our application.

The PowerModeChanged event (and our custom event) basically depends on the WM_POWERBROADCAST window message.

PowerModeChanged event

The PowerModeChanged event uses the PowerModes enumeration which defines the identifiers for power mode events reported by the Operating System. First, we need to register this event in our application to receive notifications from the Operating System.

Remember it’s a static event, so we must detach our event handler when our application is disposed; otherwise, it will result in memory lakes.

Using a window message (WM_POWERBROADCAST)

Windows uses the WM_POWERBROADCAST message to inform applications of changes related to power. An application must have to be registered to receive notification messages from Windows. We can find out information about an event while examining the wParam parameter of the window message. A complete list of events is available in the Power Management documentation. To use Win32 window messages in our C# WPF application, we will use the HwndSourceHook delegate which is in the System.Windows.Interop namespace and the assembly is in PresentationCore.dll. For interoperation between Windows Presentation Foundation and Win32, we will use the WindowInteropHelper class.

When the system wakes up because of the result of a timer signal, a window triggers the message PBT_APMRESUMEAUTOMATIC to the application and will set an unattended idle timer for two minutes (based on your settings). This timer gives the applications enough time to call the SetThreadExecutionState() function to indicate the Operating System that you are back with full functionality. If we do not do anything in this unattended idle time interval, then the system will return to the sleep state assuming that the system is no more required.

To synthesize a keystroke, we will use the keybd_event() function to generate a keyboard message when the system is resumed because of the result of a timer signal. In this way, we will definitely get the PBT_APMRESUMESUSPEND window message when we receive the PBT_APMRESUMEAUTOMATIC message.

Using Custom Events for Power Management in WPF

On the basis of your requirements, you can also add custom events in your application. To register a new routed event with the Windows Presentation Foundation (WPF) event system, use RegisterRoutedEvent method of EventManager class.

Using the code

Here is the complete code for all of the three methods mentioned above. In the sample application, it will ask to enter the number of seconds to resume after Sleep or Hibernate. Below is the output of the sample application:

You can get this complete WPF sample application in the attachment. I did not test this on XP, but I did on Vista and Windows 7:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Interop;


namespace SleepHibernateExercise
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private static long SECONDS = 10000000;
        const int WM_POWERBROADCAST = 0x0218;
        const int PBT_APMPOWERSTATUSCHANGE = 0x000A;
        const int PBT_APMSUSPEND = 0x0004;
        const int PBT_APMRESUMESUSPEND = 0x0007;
        const int PBT_POWERSETTINGCHANGE = 0x8013;
        const int PBT_APMRESUMEAUTOMATIC = 0x0012;
        const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;

        const int VK_NONAME = 0x00FC;
        private const byte KEYEVENTF_SILENT = 0x0004;
 
        [Flags]
        public enum EXECUTION_STATE : uint
        {
            ES_CONTINUOUS = 0x80000000,
            ES_DISPLAY_REQUIRED = 0x00000002,
            ES_SYSTEM_REQUIRED = 0x00000001,
            ES_AWAYMODE_REQUIRED = 0x00000040
        }
 
        public delegate void TimerCompleteDelegate();
        public event TimerCompleteDelegate OnTimerCompleted;

        private IntPtr handle;
        private uint INFINITE = 0xFFFFFFFF;

        private DateTime StartTime;
        private DateTime EndTime;


        [DllImport("kernel32.dll")]
        static extern IntPtr CreateWaitableTimer(IntPtr lpTimerAttributes, 
               bool bManualReset, string lpTimerName);

        [DllImport("kernel32.dll")]
        static extern bool SetWaitableTimer(IntPtr hTimer, [In] ref long ft, 
               int lPeriod, TimerCompleteDelegate pfnCompletionRoutine, 
               IntPtr pArgToCompletionRoutine, bool fResume);

        [DllImport("kernel32.dll")]
        static extern bool CancelWaitableTimer(IntPtr hTimer);

        [DllImport("kernel32.dll")]
        static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);

        [DllImport("kernel32.dll", SetLastError = true, 
           CallingConvention = CallingConvention.Winapi, 
           CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CloseHandle(IntPtr hObject);


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

        [DllImport("user32.dll")]
        static extern void keybd_event(byte bVk, byte bScan, 
                      uint dwFlags, int dwExtraInfo);

        public Window1()
        { 
            InitializeComponent(); 
        }

        ~Window1()
        {
            Dispose();
        }

        private IntPtr MessageProc(IntPtr hwnd, int msg, IntPtr wParam, 
                                   IntPtr lParam, ref bool handled)
        {

            if (msg == WM_POWERBROADCAST)
            {
                if (wParam.ToInt32() == PBT_APMSUSPEND)
                {
                    StartTime = DateTime.Now;
                    listBox1.Items.Add("Suspend: " + 
                             StartTime.ToLongTimeString());

                    RaiseSystemSuspendingEvent(); //for custom event
                }

                if (wParam.ToInt32() == PBT_APMRESUMESUSPEND)
                {
                    listBox1.Items.Add("Resume: " + EndTime.ToLongTimeString());

                    try
                    {
                        CancelWaitableTimer(handle);
                    }
                    catch (Exception ex)
                    {
                        listBox1.Items.Add("Exception while calling " + 
                                           "CancelWaitableTimer(): " + 
                                           ex.Message);
                    }

                    RaiseSystemResumingEvent(); //for custom event

                }

                if (wParam.ToInt32() == PBT_APMRESUMEAUTOMATIC)
                {
                    EndTime = DateTime.Now;
                    listBox1.Items.Add("Auto Resume: " + 
                                       DateTime.Now.ToLongTimeString());
                    SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | 
                                            EXECUTION_STATE.ES_CONTINUOUS);
                    SetThreadExecutionState(EXECUTION_STATE.ES_DISPLAY_REQUIRED);

                    if (StartTime.CompareTo(EndTime) != 0)
                    {
                        TimeSpan span = EndTime.Subtract(StartTime);
                        listBox1.Items.Add("Time Difference (milli seconds): " + 
                                           span.Milliseconds);
                        listBox1.Items.Add("Time Difference (seconds): " + 
                                           span.Seconds);
                        listBox1.Items.Add("Time Difference (minutes): " + 
                                           span.Minutes);
                        listBox1.Items.Add("Time Difference (hours): " + 
                                           span.Hours);
                        listBox1.Items.Add("Time Difference (days): " + 
                                           span.Days);

                        listBox1.Items.Add("");
                        listBox1.Items.Add("Total time taken (between " + 
                                           "Sleep/Hibernate and Resume): " + 
                                           span.ToString());
                    }

                    keybd_event(VK_NONAME, 0, KEYEVENTF_SILENT, 0);
                    // to generate the PBT_APMRESUMESUSPEND window message from OS

                }

            }

        	return IntPtr.Zero;
    	}

	    private void Window_Loaded(object sender, RoutedEventArgs e)
	    {

	        SystemEvents.PowerModeChanged += 
	          new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);

	        WindowInteropHelper helper = new WindowInteropHelper(this);
	        HwndSource source = HwndSource.FromHwnd(helper.Handle);
	        source.AddHook(new HwndSourceHook(this.MessageProc));

	        this.SystemSuspending += new RoutedEventHandler(Window1_SystemSuspending);
	        this.SystemResuming += new RoutedEventHandler(Window1_SystemResuming); 
	
	    }
	
	    private void Dispose()
	    { 
	        //to detach event handler because its a static event
	        SystemEvents.PowerModeChanged -= 
	          new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
	
	        GC.SuppressFinalize(this);
	    }
	
	    private void SystemEvents_PowerModeChanged(object sender, 
	                              PowerModeChangedEventArgs e)
	    {
	        // User is putting the system into standby 
	        if (e.Mode == PowerModes.Suspend)
	        {
	            listBox1.Items.Add("Got Suspend message (PowerModeChanged " + 
	                               "event): " + DateTime.Now.ToLongTimeString());
	            listBox1.Items.Add("");
	        }
	
	        // User is putting the system into resume from standby 
	        if (e.Mode == PowerModes.Resume)
	        {
	            listBox1.Items.Add("Got Resume message " + 
	                               "(PowerModeChanged event): " + 
	                               DateTime.Now.ToLongTimeString());
	            listBox1.Items.Add("");
	        }
	
	    }
	
	    private void SleepButton_Click(object sender, RoutedEventArgs e)
	    {
	
	        //for (suspend)Sleep
	        if ((SecondsTextBox.Text != "") && 
	                 (Convert.ToInt32(SecondsTextBox.Text) > 0))
	        {
	            SetTimer(Convert.ToInt32(SecondsTextBox.Text), PowerState.Suspend);
	        }
	    }
	
	 
	    private void HibernateButton_Click(object sender, RoutedEventArgs e)
	    {
	        //for Hibernate
	        if ((SecondsTextBox.Text != "") && 
	            (Convert.ToInt32(SecondsTextBox.Text) > 0))
	        {
	            SetTimer(Convert.ToInt32(SecondsTextBox.Text), 
	                                     PowerState.Hibernate);
	        }
	    }
	
	    private void AbortButton_Click(object sender, RoutedEventArgs e)
	    {
	
	        //cancel operation
	        TimerCancel(); 
	    }
	 
	    public void SetTimer(long Interval,PowerState ps)
	    {
	
	        // Creating the timer delegate
	        TimerCompleteDelegate TimerComplete = 
	                     new TimerCompleteDelegate(TimerCompleted); 
	
	        listBox1.Items.Add("System is going to " + ps + 
	           " with set the timer to be back after " + 
	           Interval + " seconds");
	        Interval = -Interval * SECONDS;
	
	        // Creating the timer 
	        handle = CreateWaitableTimer(IntPtr.Zero, true, 
	                                     "WaitableTimer");
	        if (Marshal.GetLastWin32Error() != 0)
	        {
	            listBox1.Items.Add("Error: " + 
	                     Marshal.GetLastWin32Error().ToString());
	        }

	        SetWaitableTimer(handle, ref Interval, 0, TimerComplete, 
	                         IntPtr.Zero, true);
	        if (Marshal.GetLastWin32Error() != 0)
	        {
	            listBox1.Items.Add("Error: " + 
	                     Marshal.GetLastWin32Error().ToString());
	        }
	
	
	        listBox1.Items.Add("Timer set: " + 
	                           DateTime.Now.ToLongTimeString());
	        // Starting a new thread which change the current system state 
	        Thread tSystemState = new Thread(ChangeSystemState); 
	        tSystemState.Start(ps);
	
	        Thread t_Wait = new Thread(new ThreadStart(WaitTimer)); 
	        t_Wait.Start(); 
	
	    }
	
	    private void ChangeSystemState(Object obj)
	    {
	        PowerState pp = (PowerState)obj;
	        if (System.Windows.Forms.Application.SetSuspendState(pp, 
	                                             true, false) == false)
	        {
	            //listBox1.Items.Add("Unable to Change the state of system: " + 
	            //           DateTime.Now.ToLongTimeString());
	        }
	    }
	
	    private void WaitTimer()
	    {
	
	        SleepEx(INFINITE, true);
	        CloseHandle(handle);
	
	        // Raising event Timer Completed 
	        if (OnTimerCompleted != null)
	        {
	            OnTimerCompleted();
	        }
	    }
	
	
	    private void TimerCompleted()
	    {
	        System.Windows.Forms.Application.Run();
	        // Routine executed once the timer has expired.
	        // This is executed independently of the 
	        // program calling this class implementation
	        // of the OnTimerCompleted Event
	
	        listBox1.Items.Add("Timer is complete: " + 
	                           DateTime.Now.ToLongTimeString()); 
	    }

	    public void TimerCancel()
	    {
	        listBox1.Items.Add("Cancel the Timer");
	        CancelWaitableTimer(handle);
	    }
	
	    private void CloseButton_Click(object sender, RoutedEventArgs e)
	    {
	        GC.Collect();
	        System.Windows.Application.Current.Shutdown();
	        Environment.Exit(0);
	    }
	
	    //custom events
	    void Window1_SystemSuspending(object sender, RoutedEventArgs e)
	    {
	        listBox1.Items.Add("System is entering suspend mode" + 
	                           " (Custom Event): " + 
	                           DateTime.Now.ToLongTimeString());
	    }
	
	    void Window1_SystemResuming(object sender, RoutedEventArgs e)
	    {
	        listBox1.Items.Add("System is resuming from " + 
	                           "a suspend mode (Custom Event): " + 
	                           DateTime.Now.ToLongTimeString());
	    }

	    //For custom Suspend event
	    // Register the SystemSuspend custom event
	    public static readonly RoutedEvent SystemSuspendingEvent = 
	            EventManager.RegisterRoutedEvent(
	                                    "SystemSuspendingEvent",
	                                    RoutingStrategy.Bubble,
	                                    typeof(RoutedEventHandler),
	                                    typeof(Window1));
	
	    //Provide CLR accessors for the event
	    public event RoutedEventHandler SystemSuspending
	    {
	
	        // add or remove handlers to this event
	        add 
	        { 
	            AddHandler(SystemSuspendingEvent, value); 
	        }
	        remove 
	        { 
	            RemoveHandler(SystemSuspendingEvent, value); 
	        }
	    }
	
	    // it will be called when window wants to raise the event 
	    private void RaiseSystemSuspendingEvent()
	    {
	
	        RoutedEventArgs newEventArgs = 
	          new RoutedEventArgs(Window1.SystemSuspendingEvent, this);
	        RaiseEvent(newEventArgs);
	    }

	    //for custom Resume event 
	    static readonly RoutedEvent SystemResumingEvent = 
	             EventManager.RegisterRoutedEvent(
	                          "SystemResumingEvent", 
	                          RoutingStrategy.Bubble, 
	                          typeof(RoutedEventHandler), 
	                          typeof(Window1));
	
	    //Provide CLR accessors for the event
	    public event RoutedEventHandler SystemResuming
	    {
	        add
	        {
	            AddHandler(SystemResumingEvent, value);
	        }
	        remove
	        {
	            RemoveHandler(SystemResumingEvent, value);
	        }
	    }
	
	    // it will be called when window wants to raise the event 
	    void RaiseSystemResumingEvent()
	    {
	        RoutedEventArgs newEventArgs = 
	          new RoutedEventArgs(Window1.SystemResumingEvent, this);
	        RaiseEvent(newEventArgs);
	    }
	}
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Najam ul Hassan
Software Developer (Senior)
United States United States
I have done Masters in Computer Sciences and for last ten years working in Visual C++ and C# on different domains.
Keep programming... . . .

Comments and Discussions

 
QuestionAwesome work PinmemberMember 1123127313-Nov-14 7:29 
GeneralMy vote of 5 PinmemberMember 402933712-Jun-13 17:37 
SuggestionMinor suggestion. Pinmemberhack_guru2-Jan-13 14:12 
GeneralMy vote of 5 PinmemberRenn Valo10-Dec-12 7:59 
GeneralApplication close error on XP:A callback was made on a garbage collected delegate PinmemberOzan 226-May-10 21:17 
QuestionDoes the WEPOS operating system support Kernel32 funtions and your code? PinmemberSumeetSD14-Dec-09 6:44 
GeneralDoes it work on Win 7 64 bit OS Pinmemberyu_feng_yu10-Dec-09 21:22 
Generalresume time question Pinmemberscottshui25-Sep-09 3:45 
GeneralRe: resume time question PinmemberNajam ul Hassan25-Sep-09 5:42 
Generalxp Pinmemberdtm21-Aug-09 21:07 
Generalnice article PinmemberBryan_20-Aug-09 13:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 20 Aug 2009
Article Copyright 2009 by Najam ul Hassan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid