Freigeben über


Power Management in Windows XP

This topic introduces you to power management for mobile PC applications running on Windows Vista and Windows XP. Using APIs available on the platform, you will explore how applications can work more efficiently on mobile PCs.

Summary

This article teaches you how to enable your Microsoft .NET Framework applications to detect the power source and to be notified of changes to the power source. This article also provides you with the techniques you'll need to allow your application to consume less power when required.

The Importance of Power

With the increase in computing power and memory, there has not been an equivalent increase in the battery life of mobile devices. The battery technologies that are currently deployed in most mobile PCs have not dramatically changed in many years. Increasing the length of time that a user can work with a laptop or Tablet PC without needing to change batteries or plug into an AC power outlet can dramatically improve that user's experience.

Not surprisingly, battery life is incredibly dependant on the software running on the device. Certain tasks, such as graphics or audio intensive operations, drain the battery at a much faster rate than simply displaying a window on the screen. It is therefore the responsibility of software developers to write applications that can increase the life of the battery when the user is working on a mobile PC.

By following the guidance in this document, you can improve the experience users have when running your applications on a mobile PC.

Knowing Your Power Source

The first step is for your application to determine if it is running on a computer that has batteries. You can then determine if the computer is currently plugged into an AC power supply or running on battery power. Once you know these two facts, you can adjust the behavior of your application accordingly.

The .NET Framework 1.x does not provide this information for you. Instead, you will use the Platform Invoke (P/Invoke) mechanism to call a Win32 function, GetSystemPowerStatus, that takes a pointer to a SYSTEM_POWER_STATUS structure.

You declare these in managed code like this:

[StructLayout(LayoutKind.Sequential)]
public class SYSTEM_POWER_STATUS
{
  public byte ACLineStatus;
  public byte BatteryFlag;
  public byte BatteryLifePercent;
  public byte Reserved1;
  public Int32 BatteryLifetime;
  public Int32 BatteryFullLifetime;
}

public enum ACLineStatus : byte
{
  Battery = 0,
  AC      = 1,
  Unknown = 255
}

[FlagsAttribute]
public enum BatteryFlag : byte
{
  High        = 1,
  Low         = 2,
  Critical    = 4,
  Charging    = 8,
  NoSystemBattery = 128,
  Unknown     = 255
}

[DllImport("Kernel32.DLL", CharSet = CharSet.Auto,
     SetLastError = true)]
  private extern static
    bool GetSystemPowerStatus(
        SYSTEM_POWER_STATUS SystemPowerStatus);

Notice that the structure has been declared as a .NET Framework managed class. Classes in the .NET Framework are always passed by reference, and this saves you the concerns associated with dealing with a pointer for now. The ReportPowerStatus method-in the following sample code-shows how to call this function and extract the power details from the SYSTEM_POWER_STATUS class.

protected virtual string ReportPowerStatus()
{
  string status = string.Empty;

  SYSTEM_POWER_STATUS powerStatus;
  powerStatus = new SYSTEM_POWER_STATUS();

  bool result = GetSystemPowerStatus(powerStatus);

  if (result)
  {
    StringBuilder statusMsg = new StringBuilder();

    statusMsg.Append("AC Power status: ");
    if (ACLineStatus.Battery ==
        powerStatus.ACLineStatus)
    {
        statusMsg.Append("Offline");
    }
    else if (ACLineStatus.AC ==
        powerStatus.ACLineStatus)
    {
        statusMsg.Append("Online");
    }
    else
    {
        statusMsg.Append("Unknown");
    }

    statusMsg.Append(System.Environment.NewLine);
      statusMsg.Append("Battery Charge status: ");
      statusMsg.Append(powerStatus.BatteryFlag.ToString());

      statusMsg.Append(System.Environment.NewLine);
      statusMsg.Append("Battery Charged: ");
      if (255 == powerStatus.BatteryLifePercent)
      {
          statusMsg.Append("Unknown");
      }
      else
      {
          statusMsg.Append(
              powerStatus.BatteryLifePercent.ToString());
          statusMsg.Append("%");
      }

      statusMsg.Append(System.Environment.NewLine);
      statusMsg.Append("Battery Life left: ");

      if (-1 == powerStatus.BatteryLifetime)
      {
          statusMsg.Append("Unknown");
      }
      else
      {
          statusMsg.Append(
              powerStatus.BatteryLifetime.ToString());
          statusMsg.Append(" seconds");
      }

      statusMsg.Append(System.Environment.NewLine);
      statusMsg.Append("A Full battery will last: ");
      if (-1 == powerStatus.BatteryFullLifetime )
      {
          statusMsg.Append("Unknown");
      }
      else
      {
          statusMsg.Append(
          powerStatus.BatteryFullLifetime.ToString());
          statusMsg.Append(" seconds");
      }
      statusMsg.Append(System.Environment.NewLine);

      Console.Out.WriteLine(statusMsg);
      status = statusMsg;

  }
  else
  {
      Console.Out.WriteLine("Failed to get power status");
  }
  Console.Out.WriteLine();
  return status;
}

Note  The BatteryLifetime and BatteryFullLifetime fields may not be available on some devices. It is therefore preferable for you to use the ACLineStatus and BatteryLifePercent fields to find out the current state of the battery.

Knowing if the computer is running on battery power is only the first step. Next, you must determine if the devices on the computer are currently accessible. Many computers have the ability to shut down the screen and hard disk after a certain period of inactivity, and you can help improve the battery life by not attempting to use a device if the computer has shut it down in this fashion. In most situations, your application should wait for activity to wake a device up before attempting to utilize the device.

The status of a device is also not currently available from the .NET Framework 1.x. There is another Win32 function that you can call to retrieve this information: GetDevicePowerState. The GetDevicePowerState function takes two parameters, a handle to the device for which you are requesting information and a Boolean flag to indicate if the device is active or not. The GetDevicePowerState method can be imported in C# as shown in this code example:

[DllImport("Kernel32.DLL", CharSet = CharSet.Auto,
       SetLastError=true)]
  private extern static
      bool GetDevicePowerState(
          IntPtr hDevice,
          out bool fOn);

Once you have imported the function you call it to discover the current state of the device. The example function shown here, ReportDiskStatus, demonstrates how to retrieve the state of the disk. ReportDiskStatus uses the assembly files to determine if the disk from which the assembly is running is currently active. In your application, you will probably already have a file handle you can use to check if the disk containing that file is currently powered.

protected string ReportDiskStatus()
{
    string status = string.Empty;
    bool fOn = false;

    Assembly assembly = Assembly.GetExecutingAssembly();
    FileStream[] files = assembly.GetFiles();
    if (files.Length > 0)
    {
        IntPtr hFile = files[0].Handle;
        bool result = GetDevicePowerState(hFile, out fOn);
        if (result)
        {
            if (fOn)
            {
                status = "Disk is powered up and spinning";
            }
            else
            {
                status = "Disk is sleeping";
            }
        }
        else
        {
            status = "Cannot get Disk Status";
        }
        Console.WriteLine(status);
    }
    return status;
}

Note  You cannot use the GetDevicePowerState method to retrieve information about the display device.

With this information, you can encapsulate the information into a class that your application can use.

Now that your application has information about the current state of the power supply and devices, you will find it useful to know when this state changes.

Receiving Notifications When the Source Changes

The .NET Framework 1.x provides you with a PowerModeChanged event in the SystemEvents class that can be found in the Microsoft.Win32 namespace. The PowerModeChanged event provides you with a PowerModeChangedEventArgs object, which contains one property of interest to you. The Mode property will equal one of three possible values as described in the PowerModes enumeration.

Member name Description

Resume

The operating system is about to resume.

StatusChange

The power mode status of the operating system has changed. This might indicate a weak or charging battery, a transition from AC power to battery, or other change in the status of the system power supply.

Suspend

The operating system is about to be suspended.

 

For most applications this is all the information you will need.

On a StatusChange event, you can call the GetSystemPowerStatus function (as discussed in the previous section) to find out about the new power status. Your application can then adjust behavior to respond to the changes. If the computer is switching over to run on battery power, then your application should change its mode of operation to help preserve the battery life. The following section discusses how this can be achieved.

The event will be raised with Suspend mode when the PC is hibernating or standing by. Your application should save any important data as efficiently as possible; this will be discussed further in a following section.

Another topic that will be discussed in more detail later in this document is how to handle the event when it is raised with Resume mode. This event will occur when the computer is coming out of a hibernate or stand-by mode. In essence you need to load any data you saved and ensure your application is ready to interact as quickly as possible.

The PowerModeChanged event provides a subset of the events that the Windows platform provides. To receive the full gamut of events you need to trap the WM_POWERBROADCAST message. For most applications the PowerModeChanged event provides all the information you need.

The WM_POWERBROADCAST message contains the event information in the wParam of the message. There are 13 possible events. You can use the message and the events in managed code by declaring them as follows:

const int WM_POWERBROADCAST = 0x0218;

const int PBT_APMQUERYSUSPEND         =    0x0000;
const int PBT_APMQUERYSTANDBY         =    0x0001;
const int PBT_APMQUERYSUSPENDFAILED   =    0x0002;
const int PBT_APMQUERYSTANDBYFAILED   =    0x0003;
const int PBT_APMSUSPEND              =    0x0004;
const int PBT_APMSTANDBY              =    0x0005;
const int PBT_APMRESUMECRITICAL       =    0x0006;
const int PBT_APMRESUMESUSPEND        =    0x0007;
const int PBT_APMRESUMESTANDBY        =    0x0008;
const int PBT_APMBATTERYLOW           =    0x0009;
const int PBT_APMPOWERSTATUSCHANGE    =    0x000A;
const int PBT_APMOEMEVENT             =    0x000B;
const int PBT_APMRESUMEAUTOMATIC      =    0x0012;

The APMSUSPEND, APMRESUMESUSPEND, and APMPOWERSTATUSCHANGE events are equivalent to the Suspend, Resume, and StatusChange events (respectively) in the .NET Framework's PowerModeChanged event.

There are several ways you can trap the WM_POWERBROADCAST message. If you have a main application window you can override the WndProc method of the form:

protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);

    if (WM_POWERBROADCAST == m.Msg)
    {
        // Check the status and act accordingly.
    }
}

However, if your application is a console application or a Windows Service without a window, then this is not an option. In this case, the best option is to install a filter on the Application message loop. In order to do this, you will to declare a class that implements IMessageFilter. The following example outputs the event to the console.

public class PowerMessageFilter : IMessageFilter
{
  protected void ReportPowerChange(int reason)
  {
      string report = string.Empty;
      switch (reason)
      {
          case PBT_APMQUERYSUSPEND:
              report = "Request for permission to suspend.";
              break;
          case PBT_APMQUERYSTANDBY:
              report = "Request for permission to stand by.";
              break;
          case PBT_APMQUERYSUSPENDFAILED:
              report = "Suspension request denied. ";
              break;
          case PBT_APMQUERYSTANDBYFAILED:
              report = "Stand by request denied.";
              break;
          case PBT_APMSUSPEND:
              report = "System is suspending operation ";
              break;
          case PBT_APMSTANDBY:
              report = "System is standing by ";
              break;
          case PBT_APMRESUMECRITICAL:
              report =
                "Operation resuming after critical suspension.";
              break;
          case PBT_APMRESUMESUSPEND:
              report = "Operation resuming after suspension.";
              break;
          case PBT_APMRESUMESTANDBY:
              report = "Operation resuming after stand by.";
              break;
          case PBT_APMBATTERYLOW:
              report = "Battery power is low.";
              break;
          case PBT_APMPOWERSTATUSCHANGE:
              report = "Power status has changed.";
              break;
          case PBT_APMOEMEVENT:
              report = "OEM-defined event occurred.";
              break;
          case PBT_APMRESUMEAUTOMATIC:
              report =
                 "Operation resuming automatically after event.";
              break;
      }
      Console.Out.WriteLine(report);
  }

  public bool PreFilterMessage(ref Message m)
  {
      if (WM_POWERBROADCAST == m.Msg )
      {
          Console.Out.WriteLine("Power Broadcast recieved." );
          int reason = m.WParam.ToInt32();
          ReportPowerChange(reason);
      }
      return false;
  }
}

In order to use the PowerMessageFilter class, add the filter to the Application class:

filter = new PowerMessageFilter();
Application.AddMessageFilter(filter);

When your application is notified of changes, it needs to do its best to extend the available battery life. The next section explains the consideration you should take when developing your application.

Efficient Use of Available Power

Software behavior can directly influence the consumption of power by the CPU, display, disk drives, network port (especially true with wireless networks), and other devices connected to the computer. When building software that runs on a mobile PC, consider how your application interacts with these resources. Your aim should be to minimize the usage of these resources when the computer is running on battery power.

The longevity of the battery on a mobile PC is extremely dependent on the software, so it is important to understand that the end user experience can be greatly improved by considering the power available to the computer on which your application is running.

There are a number of obvious power-hungry operations that you can avoid, such as graphics-intensive or audio operations. There are also a number of less obvious operations that have significant impact on the life of the batterydisk and network access are examples. Any background thread that carries out polling or looping tasks also affects the battery life.

The most efficient applications carry out only business-critical operations when they are running on a computer powered by batteries alone. Tasks such as background spell checking, searching online for application updates or help, audio feedback, high intensity graphical feedback, and database polling can all be switched off. Other tasks, such as incremental saves, should be done less often when you know the computer is on batteries only.

If your application performs any of these tasks, or similar tasks, then you need to think about how critical they are to the end user. Also, avoid polling operations because asking repeatedly for information keeps the processor (and any other required devices) busy, preventing them from 'stepping down' and using less power.

You must make a business decision in concert with the application architects: what are the trade-offs between the cost of having a power-draining task running versus the cost to the end user of shorter battery life?

Power Tools

There are a number of tools available now that can help you understand how much power a certain feature is consuming. Use these tools after the business decisions mentioned in the previous section have been made; for example, there is little point in shutting down the audio capabilities of an application that has a primary function as an audio player! Often the decision is not quite so obvious. Once you have an application with a feature that you can run, you can use the tools to evaluate its power consumption.

The first tool you can use is the Performance Monitor (perfmon.exe). Performance Monitor is useful for measuring the overall activity on the system and also the activity of a particular process. Using the .NET Framework, it is also easy to add your own counters to the Performance Monitor by using the PerformanceCounter class. This way, you can verify if a certain activity is using more or less resources than you expected.

The next two tools are produced by Intel. VTune can be used to determine which threads and methods are taking up the most resources. It is often the case that you find an unexpected set of methods that are using resources or keeping the processor busy. VTune helps you find those methods so you can optimize them based on the current power status of the computer.

Intel also produces the Power Evaluation Tool. This is a command line tool that helps you determine the power consumption of your application when it is running on a battery-powered computer. The Power Evaluation Tool can be used to compare two versions of an application on the same computer. This enables you to try optimizing a particular feature and compare the power consumption with a previous version that has not been optimized. You can also use the Power Evaluation Tool to compare the different behaviors your application exhibits when running in two different power settings.

The Intel tools can be obtained by registering for the Intel Software Developer Dispatch at http://intelmktg.com/sdd/.

When you use any of these tools, it is important to consider the length of time for which you run the tests. You get more detailed and accurate information from running longer tests with well-defined workloads (activities for your application). Short tests provide less value. Set up a computer on which you can run the tests for one or two hours and collect the results. This gives you a far better understanding of the power consumption of your application through its lifetime.

Handling Power-Saving Events

The focus so far has been on conserving the battery life while your application is running. There is an additional consideration that you should make: how your application behaves when the computer suspends operation. There are two key scenarios to consider here:

  • When the computer is idle for a certain period of time, the active Power Scheme is likely to specify that the hardware either goes into Stand By or Hibernate mode.
  • When the user takes an action that puts the computer into a suspend operation, such as shutting the lid of a laptop or pressing the power button.

There are some important considerations that you need to make when your application is notified of a suspend request. Your application needs to stop what it is doing as quickly as possible and preserve any state information.

The application should under no circumstances request any user input. There is nothing worse for a user who has closed the lid of his laptop to come back five hours later to a drained battery because an application was waiting for user input. An example would be if your application displays a message box asking if users wanted to save their documents. Don't ask users anything, save the state of the application and stop any activity that could prevent the computer from entering a suspend state.

Your application also needs to respond to a resume event, reload any saved state, and start any suspended operations. When resuming from a suspend state, the application should appear to the end user as if nothing has happened. The application should do its best to carry on exactly where it left off. Sometimes this is not as easy as it may sound. Be aware that attached devices may have changed in the period between going into suspend and resuming, so you cannot assume a removable drive or network connection is still available.

If the application was running from a network drive, or was in the process of updating information, then it may have issues with restarting. You should only notify the user of a failure to resume state as a last resort. If there is any way the application can carry on running (even with limited functionality), it should do so.

Also remember that when you resume, the computer may have a different power status than when it went into suspend. For example, the computer may suspend when on battery power and resume when connected to an AC supply. This means that the application will need to check the power status when it resumes and possibly adjust its behavior accordingly.

You should never attempt to veto the suspend request. Suspend your application quickly and enable your application to resume, aware that the environment may have changed. The kind of tasks your application might carry out on suspend would include: saving any critical work; stopping any network activity; closing any open files (especially important if the file is not on a local disk); and ceasing interaction with external devices such as Bluetooth or USB devices.

But what if the application is performing an operation that should not be suspended if the computer has been idle (received no user input) for a certain period of time? An example might be a movie playback or presentation application. Applications can stop the computer's idle counter from requesting a suspend operation. An application should never stop a user request for suspending the application.

In order to stop the computer from 'timing out' and requesting a suspend operation you can use the SetThreadExecutionState Win32 function. Be careful that you only do this when you absolutely must. You don't want to stop the computer from saving battery power and shorten the lifetime unnecessarily. Also, be sure that when your application has finished carrying out its critical task you set the execution state back; in this way the operating system can request a suspend operation and again start to preserve battery life.

As the name suggests, SetThreadExecutionState sets the execution state per thread. The return value is the execution state on the calling thread and does not indicate the state of the computer. The Windows operating system tracks the threads that have requested a certain execution state and then acts accordingly. So, if two applications have called SetThreadExecutionState to keep the display alive, the operating system will keep the display powered up until both of those applications have stated they no longer need the display.

The implication of this is that you may not need to store the value returned by SetThreadExecutionState. Instead, when you wish to relinquish control back to the operating system, you can simply call SetThreadExecutionState(ES_CONTINUOUS). As the operating system is monitoring the threads and the resources being requested, you can rest assured that if an application crashes and a thread no longer exists, the operating system will remove any previous requests from that thread.

The SetThreadExecutionState function and the EXECUTION_STATE types can be imported in C# as shown:

[FlagsAttribute]
public enum EXECUTION_STATE :uint
{
  ES_SYSTEM_REQUIRED  = 0x00000001,
  ES_DISPLAY_REQUIRED = 0x00000002,
  // Legacy flag, should not be used.
  // ES_USER_PRESENT   = 0x00000004,
  ES_CONTINUOUS       = 0x80000000,
}

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

I created the PowerClass that encapsulates the power management functions and presents this as a method:

public EXECUTION_STATE SetExecutionState(EXECUTION_STATE state)
{
  EXECUTION_STATE oldState = SetThreadExecutionState(state);
  return oldState;
}

My VisualizerForm method then uses the PowerClass method whenever the form is maximized, so as to stop the screen from blanking or the computer from going into a suspend state. When the form is maximized, the animation is always run even if the computer is on battery power.

Notice how the sample keeps track of the previous execution state and sets it back when finished.

private void VisualizerForm_SizeChanged(object sender, EventArgs e)
{
  if (null == power)
  {
      return;
  }
  if (this.WindowState == FormWindowState.Maximized)
  {
      previousState = power.SetExecutionState(
          PowerClass.EXECUTION_STATE.ES_DISPLAY_REQUIRED |
        PowerClass.EXECUTION_STATE.ES_CONTINUOUS);

      animationTimer.Enabled = true;

  }
  else
  {
      power.SetExecutionState(
          PowerClass.EXECUTION_STATE.ES_CONTINUOUS);
      if (power.RunningOnBattery)
      {
          animationTimer.Enabled = false;
      }
  }
}

For more information about how the PowerClass and VisualizerForm objects interact, see the following Putting It All Together section.

Putting It All Together

In order to provide you with some guidance as to how you can use the power management functionality in this article, I've laid out an architectural pattern for you to copy or enhance.

The PowerClass encapsulates all the Win32 functions and presents methods, properties, and an event that other classes can use to find out about the state of the power supply and events that are raised.

The IPowerAware interface defines four methods that a class must support in order to be considered power aware: RunOnBattery, RunOnAC, Suspend, and Resume. I imagine three classes that implement this interface: Beeper, OnlineUpdater, and VisualizerForm.

The PowerAwareController class maintains a list of classes that implement the IPowerAware interface. The controller uses the PowerClass to identify changes to the power status and notify the objects that implement the IPowerAware interface. If you want to use this model in your code, then you simply need to create a class that implements the IPowerAware interface and add it to an instance of a PowerAwareController.

Conclusion

This article has introduced some reasons why your application should be concerned about the power it consumes, and how you can do something to extend the battery life of the computer your application is running on. While many of the useful functions are in the Win32 APIs, they can easily be encapsulated into a managed code class and used from your .NET Framework applications.

Remember that nothing annoys your end user more than having a drained battery when they believe their computer is in Stand By or Hibernate modes. To prevent the user experiencing a drained battery, your application should always respond in a timely manner to suspend requests and never block the computer from suspending.

Here is a list of some of the things you should consider when making your application power friendly:

  • Determine the power source.
  • Never prevent a request from the system to suspend.
  • When the system resumes, your application should run smoothly.
  • When the system resumes, the available devices and power source may have changed.
  • Reduce the power consumption of your application when the power source is a battery: avoid polling operations, don't run non-critical worker threads, and avoid unnecessary device-disk, display, Bluetooth, USB, and so on-access.

It is important that you test your classes for all the possible transitions to ensure that your application will provide a great user experience for end users running on mobile PCs.

Win32 Power Management

 

 

Send comments about this topic to Microsoft

Build date: 2/8/2011