如何检测Windows系统是否支持唤醒计时器

3

我需要编程方式检测我的电脑(Windows 7 / 8)是否支持唤醒计时器。到目前为止,我已经完成以下操作:

Guid activePowerScheme = GetActivePowerSchemeGuid();
IntPtr ptrActiveGuid = IntPtr.Zero;
uint buffSize = 0;
uint res = PowerReadACValue(IntPtr.Zero, ref activePowerScheme, ref ApplicationConstants.SLEEPGUID, ref ApplicationConstants.WAKETIMERGUID, IntPtr.Zero, IntPtr.Zero, ref buffSize);

if (res == 0)
{
    IntPtr ptrName = IntPtr.Zero;
    try
    {
        ptrName = Marshal.AllocHGlobal((int)buffSize);
        res = PowerReadACValue(IntPtr.Zero, ref activePowerScheme, ref ApplicationConstants.SLEEPGUID, ref ApplicationConstants.WAKETIMERGUID, IntPtr.Zero, ptrName, ref buffSize);
        byte[] ba = new byte[buffSize];
        Marshal.Copy(ptrName, ba, 0, (int)buffSize);
        int retVal = BitConverter.ToInt32(ba, 0);

        if (retVal == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }        
    catch(Exception exp)
    {
        Logger.LogException(exp);
        return false;
    }
    finally
    {
        if (ptrName != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(ptrName);
        }
    }
}

return false;

这个方法大部分情况下都能起作用,但是当我重置电源计划设置时,它就会表现出不稳定性。我也尝试了以下方法:

Guid currentPowerSchemeGuid = GetActivePowerSchemeGuid();

RegistryKey currentPowerSchemeKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes\" + currentPowerSchemeGuid.ToString());
if (currentPowerSchemeKey != null)
{
    RegistryKey sleepRegKey = currentPowerSchemeKey.OpenSubKey(ApplicationConstants.SLEEPGUID.ToString());
    currentPowerSchemeKey.Close();
    if (sleepRegKey != null)
    {
        RegistryKey wakeTimerRegKey = sleepRegey.OpenSubKey(ApplicationConstants.WAKETIMERGUID.ToString());
        sleepRegKey.Close();
        if (wakeTimerRegKey != null)
        {
            wakeTimerRegKey.Close();
            currentPowerSchemeKey.Close();
            return true;
        }
        else
        {
            currentPowerSchemeKey.Close();
            return false;
        }
    }
    else
    {
        currentPowerSchemeKey.Close();
        return false;
    }
}
else
{
    return false;
}

在重置电源计划设置时,这种方法不起作用,唤醒计时器GUID注册表键会被清除。有没有一种合适的方法可以检测我的系统是否支持唤醒计时器?


2
试一下,使用CreateWaitableTimer创建一个计时器,并调用SetWaitableTimerfResume设置为TRUE(超时设置为某个遥远的时间点)来激活计时器。如果不支持唤醒计时器,则函数将成功执行,并且GetLastError将返回ERROR_NOT_SUPPORTED。使用CancelWaitableTimer取消计时器。 - arx
我在Windows 8平板电脑上尝试了您的解决方案(该设备仅运行Windows 8),SetWaitableTimer没有返回ERROR_NOT_SUPPORTED。当我查看平板电脑的电源选项时,唤醒计时器选项不可见。 - Aster Veigas
2个回答

0

使用 powrprof.dll 库对我很有效:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace Namespace {

public static class PowerOptions {

    // src: https://msdn.microsoft.com/en-us/library/windows/desktop/hh448380%28v=vs.85%29.aspx
    private readonly static Guid HIGH_PERFORMANCE = new Guid("8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c"); // aka MIN_POWER_SAVINGS
    private readonly static Guid BALANCED = new Guid("381b4222-f694-41f0-9685-ff5bb260df2e"); // aka TYPICAL_POWER_SAVINGS
    private readonly static Guid POWER_SAVER = new Guid("a1841308-3541-4fab-bc81-f71556f20b4a"); // aka MAX_POWER_SAVINGS
    private readonly static Guid ACDC_POWER_SOURCE = new Guid("5d3e9a59-e9D5-4b00-a6bd-ff34ff516548");

    private readonly static Guid SLEEP_SUBCATEGORY = new Guid("238C9FA8-0AAD-41ED-83F4-97BE242C8F20");
    private readonly static Guid WAKE_TIMERS = new Guid("bd3b718a-0680-4d9d-8ab2-e1d2b4ac806d");

    public static String GetCurrentPowerPlanFriendlyName() {
        IntPtr ptrActiveGuid = IntPtr.Zero;
        int ret = PowerGetActiveScheme(IntPtr.Zero, ref ptrActiveGuid);
        if (ret == 0) {
            uint buffSize = 0;
            ret = PowerReadFriendlyName(IntPtr.Zero, ptrActiveGuid, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref buffSize);
            if (ret == 0) {
                if (buffSize == 0)
                    return "";

                IntPtr ptrName = Marshal.AllocHGlobal((int) buffSize);
                ret = PowerReadFriendlyName(IntPtr.Zero, ptrActiveGuid, IntPtr.Zero, IntPtr.Zero, ptrName, ref buffSize);
                if (ret == 0) {
                    String name = Marshal.PtrToStringUni(ptrName);
                    Marshal.FreeHGlobal(ptrName);
                    return name;
                }
                Marshal.FreeHGlobal(ptrName);
            }
        }
        throw new Win32Exception(ret, "GetCurrentPowerPlanFriendlyName");
    }

    public static PowerStatus GetPowerStatus() {
        PowerStatus ps = new PowerStatus();
        if (!GetSystemPowerStatus(ref ps))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "GetPowerStatus");
        return ps;
    }

    public static bool GetWakeTimersEnabled(PowerSource powerSource = PowerSource.Current, PowerPlan powerPlan = PowerPlan.Current) {
        int ret = 0;
        if (powerSource == PowerSource.Current) {
            PowerStatus ps = GetPowerStatus();
            if (ps.ACLineStatus == PowerLineStatus.Online)
                powerSource = PowerSource.PluggedIn;
            else
                powerSource = PowerSource.OnBattery;
        }

        if (ret == 0) {
            if (powerPlan == PowerPlan.Current) {
                IntPtr ptrPowerPlan = IntPtr.Zero;
                ret = PowerGetActiveScheme(IntPtr.Zero, ref ptrPowerPlan);

                if (ret == 0) {
                    uint value = 0;
                    if (powerSource == PowerSource.PluggedIn)
                        ret = PowerReadACValueIndex(IntPtr.Zero, ptrPowerPlan, SLEEP_SUBCATEGORY, WAKE_TIMERS, ref value);
                    else
                        ret = PowerReadDCValueIndex(IntPtr.Zero, ptrPowerPlan, SLEEP_SUBCATEGORY, WAKE_TIMERS, ref value);

                    if (ret == 0) {
                        return (value == 1);
                    }
                }
            }
            else {
                Guid guid = GetGuid(powerPlan);
                uint value = 0;
                if (powerSource == PowerSource.PluggedIn)
                    ret = PowerReadACValueIndex(IntPtr.Zero, guid, SLEEP_SUBCATEGORY, WAKE_TIMERS, ref value);
                else
                    ret = PowerReadDCValueIndex(IntPtr.Zero, guid, SLEEP_SUBCATEGORY, WAKE_TIMERS, ref value);

                if (ret == 0) {
                    return (value == 1);
                }
            }
        }
        throw new Win32Exception(ret, "GetWakeTimersEnabled");
    }

    public static Guid GetGuid(PowerPlan powerPlan) {
        if (powerPlan == PowerPlan.Balanced)
            return BALANCED;
        if (powerPlan == PowerPlan.HighPerformance)
            return HIGH_PERFORMANCE;
        if (powerPlan == PowerPlan.PowerSaver)
            return POWER_SAVER;
        throw new ArgumentException("Not a standard power plan: " + powerPlan);
    }

    [DllImport("powrprof.dll", SetLastError = true)]
    public static extern int PowerWriteACValueIndex(IntPtr RootPowerKey, Guid SchemeGuid, Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, uint AcValueIndex);

    [DllImport("powrprof.dll", SetLastError = true)]
    private static extern int PowerReadACValueIndex(IntPtr RootPowerKey, IntPtr SchemeGuid, Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, ref uint AcValueIndex);
    [DllImport("powrprof.dll", SetLastError = true)]
    private static extern int PowerReadACValueIndex(IntPtr RootPowerKey, Guid SchemeGuid, Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, ref uint AcValueIndex);

    [DllImport("powrprof.dll", SetLastError = true)]
    private static extern int PowerReadDCValueIndex(IntPtr RootPowerKey, IntPtr SchemeGuid, Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, ref uint AcValueIndex);
    [DllImport("powrprof.dll", SetLastError = true)]
    private static extern int PowerReadDCValueIndex(IntPtr RootPowerKey, Guid SchemeGuid, Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, ref uint AcValueIndex);

    [DllImport("powrprof.dll", SetLastError = true)]
    private static extern int PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr ActivePolicyGuid);

    [DllImport("powrprof.dll", SetLastError = true)]
    private static extern int PowerReadFriendlyName(IntPtr RootPowerKey, IntPtr SchemeGuid, IntPtr SubGroupOfPowerSettingsGuid, IntPtr PowerSettingGuid, IntPtr Buffer, ref uint BufferSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GetSystemPowerStatus(ref PowerStatus lpSystemPowerStatus);
}

public enum PowerPlan {
    Current,
    HighPerformance,
    Balanced,
    PowerSaver,
}

public enum PowerSource {
    Current,
    OnBattery,
    PluggedIn
}

public struct PowerStatus {

    ///<summary>The AC power status.</summary>
    public PowerLineStatus ACLineStatus;

    ///<summary>The battery charge status.</summary>
    public PowerChargeStatus BatteryFlag;

    ///<summary>Returns a value between [0 to 100] or 255 if unknown.</summary>
    public byte BatteryLifePercent;

    ///<summary>Returns a value that indicates if the system is currently conserving power.</summary>
    public PowerSaveStatus SystemStatusFlag;

    ///<summary>Number of seconds of battery life remaining, or -1 if unknown.</summary>
    public int BatteryLifeTime;

    ///<summary>Number of seconds of batter life on a full charge, or -1 if unknown.</summary>
    public int BatteryFullLifeTime;
}

public enum PowerLineStatus : byte {
    Offline = 0,
    Online = 1,
    Unknown = 255,
}

[Flags]
public enum PowerChargeStatus : byte {
    High = 1,
    Low = 2,
    Critical = 4,
    Charging = 8,
    NoBattery = 128,
    Unknown = 255,
}

public enum PowerSaveStatus : byte {
    Off = 0,
    On = 1,
}

}

0

根据arx的说法,尝试了以下代码并且它可以正常工作。

    public static bool IsWakeTimerSupported()
    {
        IntPtr timerHandle  = CreateWaitableTimer(IntPtr.Zero, true, "Wait Timer 1");
        uint retVal         = GetLastError();
        if (timerHandle != IntPtr.Zero)
        {
            CancelWaitableTimer(timerHandle);
            CloseHandle(timerHandle);
            timerHandle     = IntPtr.Zero;
        }

        //SUCCESS
        if (retVal == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

可以忽略 CancelWaitableTimer(timerHandle),因为 MSDN 文档建议使用 CloseHandle。

编辑:

public static bool IsWakeTimerSupported()
{
        IntPtr timerHandle  = CreateWaitableTimer(IntPtr.Zero, true, "Wait Timer 1");
        long interval       = 0;
        int retVal          = 0;
        if (timerHandle != IntPtr.Zero)
        {
            SetWaitableTimer(timerHandle, ref interval, 0, IntPtr.Zero, IntPtr.Zero, true);
            retVal = Marshal.GetLastWin32Error();
            WaitableTimer.CancelWaitableTimer(timerHandle);
            try
            {
                Win32.CloseHandle(timerHandle);
            }
            catch (Exception exp)
            {
                Logger.LogException(exp);
            }
            timerHandle = IntPtr.Zero;
        }

        //SUCCESS
        if (retVal == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
}

根据这篇文章http://blogs.msdn.com/b/adam_nathan/archive/2003/04/25/56643.aspx,我们不应该通过PInvoke使用GetLastError。

在Windows 8.1上,即使当前的“允许唤醒计时器”已禁用(无论是电池还是插入电源),retVal == 0。 - Loathing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接