Windows Kernel32.BatteryLifePercent = 255

3
我正在尝试构建一个Java应用程序,读取笔记本电池的状态,并在电量低时向用户发送通知。为了做到这一点,我使用jna和Kernel32本地库,正如这个问题的第一个答案所解释的那样:如何在Windows系统中获取剩余电池寿命? 运行示例,该程序会产生以下输出:
ACLineStatus: Offline
Battery Flag: High, more than 66 percent
Battery Life: Unknown
Battery Left: 0 seconds
Battery Full: 10832 seconds

在Kernel32中读取电池寿命和电量剩余字段的值是BatteryLifePercentBatteryLifeTime,它们的值分别为255(未知)和0(我不理解这个值。根据此处的Microsoft文档,未知应该是-1:https://msdn.microsoft.com/en-us/library/windows/desktop/aa373232(v=vs.85).aspx)。
我的问题是:为什么我会得到这些值?Windows电池托盘图标显示了正确的百分比,那么为什么我无法从这里获取数据?
我正在运行Windows 7 Ultimate Edition 64位。
谢谢。

如果你将它作为一个字节值读取并正确地符号扩展,而不是只是把0xff放入一个更大的值桶中,那么255就是-1。 - technomage
1
我理解255。但是我不明白0的含义。应该是-1。另外,问题是为什么Kernel32不知道我的电池状态。谢谢。 - Aurasphere
检查Kernel32函数上的映射,有人可能在某些地方大小出了错误,导致读取了错误的字节,或者在关键结构中忽略/添加填充。 - technomage
我修复了你找到的答案。实际上,getFieldOrder()只是在后来的Windows版本中添加的(最初的代码片段在XP中运行良好,但在Vista/7中不行),我得到了另一个用户的建议(10k用户可以看到已删除的答案),并在两年多以后将其添加到答案中,没有仔细测试输出结果。对此我很抱歉! - BalusC
1个回答

5
链接答案中的代码是错误的(编辑:现在已经修复)。 字段的顺序不正确,请使用getFieldOrder方法进行更改。
@Override
protected List<String> getFieldOrder() 
{
    ArrayList<String> fields = new ArrayList<String>();
    fields.add("ACLineStatus");
    fields.add("BatteryFlag");
    fields.add("BatteryLifePercent");
    fields.add("Reserved1");
    fields.add("BatteryLifeTime");
    fields.add("BatteryFullLifeTime");
    return fields;
}

此外,还需添加指定正确对齐方式的构造函数。
 public SYSTEM_POWER_STATUS()
 {
    setAlignType(ALIGN_MSVC);
 }

对齐方式也可以是 ALIGN_NONE,因为Microsoft通常会注意将数据与保留字段明确对齐。
它也可以是 ALIGN_DEFAULT,因为据我所知,Windows是用Microsoft编译器编译的,它在自然边界上对齐数据。

换句话说,该结构体经过设计自然对齐,因此不需要特定的对齐约束。


这是原始代码在我的系统上的输出结果

ACLineStatus: 离线
Battery Flag: 高,超过66%
Battery Life: 未知
Battery Left: 0秒
Battery Full: 12434秒

这是经过修正后的代码的输出结果

ACLineStatus: 离线
Battery Flag: 高,超过66%
Battery Life: 95%
Battery Left: 12434秒
Battery Full: 未知


关于为什么会发生这种情况

考虑上面的输出,我们可以重构出内存中如何填充SYSTEM_POWER_STATUS结构。

    00 08 5f 00 96 30 00 00 ff ff ff ff
    ¯¯ ¯¯ ¯¯ ¯¯ ¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯
     |  | |  |          |             |   
     |  | |  |       BatteryLifeTime  |
     |  | | Reserved1                 |
     |  | |                      BatteryFullLifeTime     
     |  | BatteryLifePercent
     |  |
     | BatteryFlags
     |
 AcLineStatus

根据原始代码的字段顺序,这是字段初始化的方式。
    00 08 5f 00 96 30 00 00 ff ff ff ff  00 00 00 00
    ¯¯ ¯¯       ¯¯¯¯¯¯¯¯¯¯¯ ¯¯           ¯¯¯¯¯¯¯¯¯¯¯
    |  |             |      |                    |
    | BatteryFlags   |     BatteryLifePercent    |
    |                |                           |
AcLineStatus         |                         BatteryLifeTime
                 BatteryFullLifeTime

这些间隙是由于默认对齐方式造成的,它们将数据对齐到其自然边界。
由于字段已重新排序,它们不再处于原始位置且连续。

关于为什么BatteryFullLifeTime未知

如果你反汇编Win7 64位中的GetSystemPowerStatus函数(可以在这里找到我的反汇编结果),并重写一个等效的C程序,你会得到如下结果。

BOOL WINAPI GetSystemPowerStatus(
  _Out_ LPSYSTEM_POWER_STATUS lpSystemPowerStatus
)
{
    SYSTEM_BATTERY_STATE battery_state;

    //Get power information
    NTStatus pi_status = NtPowerInformation(SystemBatteryState, NULL, 0, &battery_state, sizeof(battery_state));

    //Check success
    if (!NTSuccess(pi_status))
    {
        BaseSetLastNtError(pi_status);
        return FALSE;
    }

    //Zero out the input structure
    memset(lpSystemPowerStatus, sizeof(lpSystemPowerStatus), 0);

    //Set AC line status
    lpSystemPowerStatus->ACLineStatus = battery_state.BatteryPresent && battery_state.AcOnLine ? 1 : 0;

    //Set flags
    lpSystemPowerStatus->BatteryFlags   |=  (battery_state.Charging         ? 8 :    0) 
                                        |   (battery_state.BatteryPresent   ? 0 : 0x80);



    //Set battery life time percent
    lpSystemPowerStatus->BatteryLifePercent = 0xff;
    if (battery_state.MaxCapacity)
    {
        lpSystemPowerStatus->BatteryLifePercent = battery_state.RemainingCapacity > battery_state.MaxCapacity
                                                ? 100
                                                : (battery_state.RemainingCapacity*100 + battery_state.MaxCapacity/2)/battery_state.MaxCapacity;

        lpSystemPowerStatus->BatteryFlags   |=  (lpSystemPowerStatus->BatteryLifePercent > 66 ? 1 : 0) 
                                            |   (lpSystemPowerStatus->BatteryLifePercent < 33 ? 2 : 0);
    }

    //Set battery life time and full life time
    lpSystemPowerStatus->BatteryLifeTime = lpSystemPowerStatus->BatteryFullLifeTime = -1;

    if (battery_state.EstimatedTime)
        lpSystemPowerStatus->BatteryLifeTime = battery_state.EstimatedTime;
}

这表明 BatteryFullLifeTime 从未从 SYSTEM_BATTERY_STATE 结构体复制。它始终为-1。
同时,值为4的标志(临界电池电量)也从未被设置。
在较新版本的Windows中,这些问题可能已经被修复。

一个新版本

您可以在PowrProf.dll中调用CallNtPowerInformation来获取更可靠的电池状态信息。

如果您不熟悉访问Win APIs,这里有一个JNA类可以为您完成工作。

PowrProf.Java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package javaapplication5;

/**
 *
 * @author mijo
 */
import java.util.List;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.win32.StdCallLibrary;
import java.util.Arrays;

public interface PowrProf extends StdCallLibrary {

    public PowrProf INSTANCE = (PowrProf) Native.loadLibrary("PowrProf", PowrProf.class);


    public class SYSTEM_BATTERY_STATE extends Structure 
    {
        public static class ByReference extends SYSTEM_BATTERY_STATE implements Structure.ByReference {}

        public byte AcOnLine;
        public byte BatteryPresent;
        public byte Charging;
        public byte Discharging;

        public byte Spare1_0;
        public byte Spare1_1;
        public byte Spare1_2;
        public byte Spare1_3;

        public int   MaxCapacity;
        public int   RemainingCapacity;
        public int   Rate;
        public int   EstimatedTime;
        public int   DefaultAlert1;
        public int   DefaultAlert2;

        @Override
        protected List<String> getFieldOrder() 
        {
            return Arrays.asList(new String[]
            {
                "AcOnLine", "BatteryPresent", "Charging", "Discharging", 
                "Spare1_0", "Spare1_1", "Spare1_2", "Spare1_3", 
                "MaxCapacity", "RemainingCapacity", "Rate", 
                "EstimatedTime", "DefaultAlert1", "DefaultAlert2"
            });
        }

        public SYSTEM_BATTERY_STATE ()
        {
            setAlignType(ALIGN_MSVC);
        }

        public boolean isAcConnected()
        {
            return AcOnLine != 0;
        }

        public boolean isBatteryPresent()
        {
            return BatteryPresent != 0;
        }


        public enum BatteryFlow{ Charging, Discharging, None }

        public BatteryFlow getBatteryFlow()
        {
            if (Charging != 0)       return BatteryFlow.Charging;
            if (Discharging != 0)    return BatteryFlow.Discharging;

            return BatteryFlow.None;
        }

        //in mWh
        public int getMaxCapacity()
        {
            return MaxCapacity;
        }

        //in mWh
        public int getCurrentCharge()
        {
            return RemainingCapacity;
        }

        //in mW
        public int getFlowRate()
        {
            return Rate;
        }

        //in s
        public int getEstimatedTime()
        {
            return EstimatedTime;
        }

        //in s
        //-1 if not available
        public int getTimeToEmpty()
        {
            if (getBatteryFlow() != BatteryFlow.Discharging)
                return -1;

            return -getCurrentCharge()*3600/getFlowRate();
        }

        //in s
        //-1 if not available
        public int getTimeToFull()
        {
            if (getBatteryFlow() != BatteryFlow.Charging)
                return -1;

            return (getMaxCapacity()-getCurrentCharge())*3600/getFlowRate();
        }

        public double getCurrentChargePercent()
        {
            return getCurrentCharge()*100/getMaxCapacity();
        }

        public int getCurrentChargeIntegralPercent()
        {
            return (getCurrentCharge()*100+getMaxCapacity()/2)/getMaxCapacity();
        }

        @Override
        public String toString()
        {
            StringBuilder b = new StringBuilder(4096);

            b.append("AC Line? "); b.append(isAcConnected());
            b.append("\nBattery present? "); b.append(isBatteryPresent());
            b.append("\nBattery flow: "); b.append(getBatteryFlow());
            b.append("\nMax capacity (mWh): "); b.append(getMaxCapacity());
            b.append("\nCurrent charge (mWh): "); b.append(getCurrentCharge());
            b.append("\nFlow rate (mW/s): "); b.append(getFlowRate());
            b.append("\nEstimated time (from OS): "); b.append(getEstimatedTime());
            b.append("\nEstimated time (manual): "); b.append(getTimeToEmpty());
            b.append("\nEstimated time to full (manual): "); b.append(getTimeToFull());
            b.append("\nCurrent charge (percent): "); b.append(getCurrentChargePercent());
            b.append("\nCurrent charge (integral percent): "); b.append(getCurrentChargeIntegralPercent());

            return b.toString();
        }
    }

    public int CallNtPowerInformation(int informationLevel, Pointer  inBuffer, long inBufferLen, SYSTEM_BATTERY_STATE.ByReference  outBuffer, long outBufferLen);

    static final int SystemBatteryState = 5;

    public static SYSTEM_BATTERY_STATE GetBatteryState()
    {
        SYSTEM_BATTERY_STATE.ByReference battery_state = new SYSTEM_BATTERY_STATE.ByReference();

        int retVal = PowrProf.INSTANCE.CallNtPowerInformation(SystemBatteryState, Pointer.NULL, 0, battery_state, battery_state.size());

        if (retVal != 0)
            return null;

        return battery_state;
    }
}

及其使用

public static void main(String[] args) 
{
    PowrProf.SYSTEM_BATTERY_STATE sbs = PowrProf.GetBatteryState();

    System.out.println(sbs);
} 

放电时的示例输出:

AC线?false
电池是否存在?true
电池流量:放电
最大容量(mWh):35090
当前充电量(mWh):34160
流速(mW/s):-11234
预计时间(来自操作系统):10940
预计时间(手动):10946
预计充满时间(手动):-1
当前充电量(百分比):97.34
当前充电量(整数百分比):98

充电时的示例输出:

AC线?true
电池是否存在?true
电池流量:充电
最大容量(mWh):35090
当前充电量(mWh):33710
流速(mW/s):3529
预计时间(来自操作系统):-1
预计时间(手动):-1
预计充满时间(手动):1407 当前充电量(百分比):96.06
当前充电量(整数百分比):97


注意:进行插拔电源线测试时,请等待几秒钟,因为监控不是实时的。

附言:
我使用化名Mijo签署我的代码,您可以删除该注释。


非常感谢您的解释,真的很有趣,而且您显然付出了很多努力。这真的很受欢迎。不幸的是,我想我会坚持使用Kernel32,因为我需要电池百分比,而且目前我对原始物理数据不感兴趣。但这对于未来的使用来说是好事。+1并接受。 - Aurasphere
1
@Aurasphere,我加上了百分比。说实话,我之前没有加是因为我以为你知道如何计算百分比 :) - Margaret Bloom
糟糕...我的错...哈哈,谢谢!xP - Aurasphere
FYI:参考答案已经被修复。谢谢你的解释! - BalusC

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