【WindowsAPI】 Windows 11 笔记本电脑电池接口全景与使用方法(含 C++ 代码示例) - 实践

Windows 11 笔记本电脑电池接口全景与使用方法(含 C++ 代码示例)

本文系统整理 Windows 11 下可供笔记本电脑应用程序或工具获取与监听“电池相关信息”的接口与机制,涵盖:

  • 底层结构与数据来源(ACPI / 电池类驱动 / Power Manager)
  • Win32 API(同步获取 & 事件通知)
  • Power Setting Notification GUID 体系
  • 高级接口:CallNtPowerInformation
  • C++ 访问 WMI(获取设计容量、满充容量、剩余电量等)
  • C++/WinRT 调用 Windows Runtime 电池报告 API
  • 电池磨损率计算、剩余时间估算与处理策略
  • ETW 与诊断工具(概念与参考)
  • 常见问题与健壮性设计
  • 性能与刷新频率建议
  • 多电池场景与聚合策略
  • 示例代码(多种用法)

1. 基础架构与数据来源

Windows 11 中电池数据链路可分三层:

  1. 硬件层:智能电池(Smart Battery)、电池管理芯片(Fuel Gauge)、主板嵌入式控制器(EC),通过 ACPI 表(如 _BIF_BST)提供信息:设计容量(Design Capacity)、满充容量(Last Full Charge / FullChargeCapacity)、剩余容量、充/放电速率、状态标志。
  2. 驱动层:ACPI 驱动(Acpi.sys)+ 电池类驱动(Battery.sys)统一抽象,内核电源管理器(Power Manager)维护当前电源来源(交流/电池)、剩余电量百分比、估算剩余可用时间等。
  3. 上层接口:Win32 API、Power Setting 通知、WMI 类(Win32_Battery)、Windows Runtime API (Windows.Devices.Power.Battery)、命令行工具 (powercfg)、能耗分析引擎(Energy Estimation Engine,E3)、ETW 事件。

应用开发者不直接访问 ACPI,而是通过上述接口获取抽象后的信息。


2. 主要接口分类概览

类型接口/工具主要用途适用场景
Win32 基础GetSystemPowerStatus快速获取电源来源、剩余百分比、估算时间桌面程序/服务
Win32 通知RegisterPowerSettingNotification + WM_POWERBROADCAST实时电池/电源事件需无轮询的状态刷新
Win32 高级CallNtPowerInformation平台角色、休眠统计等电源策略工具
Windows RuntimeBattery.AggregateBattery.GetReport()容量(设计/满充/剩余)、充放电速率、估算剩余时间现代 C++/WinRT 或 UWP/WinUI 桌面
WMIWin32_Battery设计容量、满充容量、剩余电量、状态码运维脚本/批量采集
命令行powercfg /batteryreport历史充放电、容量漂移深度分析、报表生成
事件 GUIDGUID_BATTERY_PERCENTAGE_REMAINING细粒度变化通知节能策略、动态 UI
ETWPower Kernel Provider能耗与状态转换跟踪诊断与性能分析
Power 状态控制SetThreadExecutionState防止睡眠/屏保影响测试自动化测试场景

3. Win32 读取电池状态:GetSystemPowerStatus

函数原型:

BOOL GetSystemPowerStatus(LPSYSTEM_POWER_STATUS lpSystemPowerStatus);

结构:

typedef struct _SYSTEM_POWER_STATUS {
BYTE  ACLineStatus;        // 0 电池供电, 1 交流电, 255 未知
BYTE  BatteryFlag;         // 多标志位组合 (低电量、充电中、无电池等)
BYTE  BatteryLifePercent;  // 0–100, 255=未知
BYTE  Reserved1;
DWORD BatteryLifeTime;     // 剩余秒数估算, -1=未知
DWORD BatteryFullLifeTime; // 满电可用秒数估算, -1=未知
} SYSTEM_POWER_STATUS;

优点:

  • 调用简单,兼容性非常高。
    缺点:
  • 不提供设计容量 / 满充容量 / 剩余 mWh。
  • 不区分多电池。
  • 时间估算可能不稳定,经常为 -1。

4. 注册电源相关通知:RegisterPowerSettingNotification

用于订阅特定电源设置变化(电池百分比、电源来源、显示状态变化等)。
接口:

HPOWERNOTIFY RegisterPowerSettingNotification(
HANDLE hRecipient,
const GUID* PowerSettingGuid,
DWORD Flags  // DEVICE_NOTIFY_WINDOW_HANDLE 或 DEVICE_NOTIFY_SERVICE_HANDLE
);

常用 GUID:

  • GUID_ACDC_POWER_SOURCE:AC/DC 电源切换。
  • GUID_BATTERY_PERCENTAGE_REMAINING:电池剩余百分比改变(DWORD)。
  • GUID_CONSOLE_DISPLAY_STATE:显示器策略状态变化。
  • GUID_SESSION_DISPLAY_STATUS:当前会话的显示开关状态。
  • GUID_SYSTEM_AWAYMODE:Away 模式转换。
  • GUID_ENERGY_SAVER_STATUS:节电模式启/停(Windows 10+)。

接收机制:窗口或服务处理 WM_POWERBROADCASTwParam == PBT_POWERSETTINGCHANGElParam 指向 POWERBROADCAST_SETTING

typedef struct {
GUID PowerSetting;
DWORD DataLength;
UCHAR Data[1];
} POWERBROADCAST_SETTING;

5. CallNtPowerInformation 高级访问

接口:

NTSTATUS CallNtPowerInformation(
POWER_INFORMATION_LEVEL InformationLevel,
PVOID InputBuffer,
ULONG InputBufferLength,
PVOID OutputBuffer,
ULONG OutputBufferLength
);

用途示例:

  • 查询平台角色(PowerPlatformRole):Desktop / Mobile / Slate 等。
  • 休眠统计、空闲状态信息。
    对电池容量直接支持有限,但可辅助判断设备类型,对策略适配有用。

6. Windows Runtime (C++/WinRT) 电池报告

命名空间:Windows::Devices::Power

核心类:Battery
关键方法:GetReport() 返回 BatteryReport,包含:

  • RemainingCapacityInMilliwattHours
  • FullChargeCapacityInMilliwattHours
  • DesignCapacityInMilliwattHours
  • ChargeRateInMilliwatts(可能为空)
  • Status (BatteryStatus::Charging, Discharging, Idle, NotPresent)
  • EstimatedRemainingDuration

优点:

  • 支持多电池聚合 Battery::AggregateBattery
  • 提供比 Win32 更完整的容量数据。
    缺点:
  • 需 C++/WinRT 或 C# 环境,传统纯 Win32 程序需额外配置。
  • 某些字段在设备上可能返回空(std::optional/空指针)。

7. WMI 查询(C++ COM)

类:Win32_Battery
常见字段:

  • EstimatedChargeRemaining (%)
  • DesignCapacity (mWh)
  • FullChargeCapacity (mWh)
  • BatteryStatus(数值:2=充电,3=放电,4=空闲)
  • TimeToFullCharge(分钟估算)
  • ExpectedLife(可能为 0 或未提供)

缺点:

  • 有些设备(尤其部分新平台或驱动简化)会返回空集。
  • 数据可能与 WinRT 略有差异。

8. 电池磨损率计算与使用策略

磨损率(Wear Level):

Wear(%) = (1 - FullChargeCapacity / DesignCapacity) * 100

注意:

  • 初期可能满充容量略低再逐渐上升(校准期)。
  • 若设计容量缺失或为 0,需回退逻辑:仅显示满充容量与剩余容量绝对值。

使用建议:

  • 记录每日 FullChargeCapacity 并绘制趋势图(可本地 SQLite 或文件日志)。
  • 提前预警:磨损率 > 30% 提示用户电池性能显著下降。

9. 剩余时间估算与波动

BatteryLifeTime(Win32)、EstimatedRemainingDuration(WinRT)可能为未知:
原因:

  • 硬件不提供实时放电速率。
  • 负载波动剧烈(CPU/GPU 瞬时升降)。
    处理策略:
  • UI 仅在多次采样都有效时显示“剩余时间”。
  • 对时间做指数移动平均(EMA)平滑,防止跳动。

10. 多电池场景(如平板 + 键盘底座电池)

Windows Runtime 聚合电池自动整合各模块剩余容量。不要简单用 WMI 遍历并加总:不同电池放电速率、优先级可能不同。
建议:

  • 优先使用 Battery::AggregateBattery
  • 如果必须手动聚合(通过多个 WinRT Battery 对象):分别取剩余 mWh 与满充 mWh,计算加权总百分比:
TotalPercent = Σ(Rem_i) / Σ(Full_i) * 100

并考虑某一电池不存在时跳过。


11. 事件驱动 vs 轮询

不推荐 <1 秒轮询:

  • 增加 CPU 唤醒次数,反而影响耗电。
  • Win32 可用 GUID 通知;WinRT 可订阅 ReportUpdated 事件。
    建议策略:
  • 事件触发即更新 UI。
  • 定期(例如 30–60 秒)进行一次数据完整刷新(容量/磨损率)。

12. 兼容与错误处理

场景与策略:

场景现象处理
台式机或无电池设备Win32 百分比=255 或 WMI 空标记“无电池”禁用逻辑
设计容量缺失WMI 返回空或 0隐藏磨损率,仅显示剩余/满充
估算时间未知-1 或空值UI 显示 “–” 或 “未知”
多电池部分丢失某一报告字段 null跳过该电池或标注“部分数据缺失”
WinRT API 初始化失败C++/WinRT 环境缺少支持回退到 Win32 + WMI
WMI 超时远程查询缓慢设置超时并缓存上次值

13. 性能与资源控制

  • 事件驱动减少轮询成本。
  • 采样间隔建议:普通状态 30–60 秒;充电/低电量时缩短到 10–15 秒。
  • 对历史记录写入采取批次(缓冲 N 次再写入文件)。
  • UI 层避免频繁重绘(只在百分比变化 ≥1% 或电源来源改变时)。

14. ETW / powercfg(概念简述)

虽然本文重点是可直接在 C++ 中调用的接口,仍概述几个诊断工具:

  • powercfg /batteryreport:生成 HTML,含每日充放电曲线、磨损记录、使用历史。
  • powercfg /energy:分析电源效率问题。
  • powercfg /sleepstudy:Modern Standby 待机耗电分析。
  • ETW Provider:Microsoft-Windows-Kernel-Power(跟踪睡眠转换、热事件)。
    高级开发可通过 StartTrace / EnableTraceEx 等方式订阅,但实现复杂且少直接用于显示电池百分比。

15. C++ 代码示例集

以下示例覆盖多种访问方式与策略。

本目录包含若干示例:
1. GetBatteryStatus.cpp          使用 GetSystemPowerStatus
2. PowerSettingNotification.cpp  注册 GUID 监听电池百分比
3. WmiBatteryQuery.cpp           通过 WMI 获取设计/满充/剩余容量
4. BatteryWearAndSmoothing.cpp   计算磨损率与平滑剩余时间
5. CppWinRtBatteryReport.cpp     使用 C++/WinRT 获取 BatteryReport
6. ServiceStyleNotification.cpp  服务句柄监听电源事件
7. CombinedStrategy.cpp          综合回退和聚合策略
#include <windows.h>
  #include <iostream>
    #include <string>
      std::string AcLineStatusStr(BYTE s) {
      switch (s) {
      case 0: return "电池供电";
      case 1: return "交流电";
      case 255: return "未知";
      default: return "未定义";
      }
      }
      int main() {
      SYSTEM_POWER_STATUS sps{};
      if (!GetSystemPowerStatus(&sps)) {
      std::cerr << "GetSystemPowerStatus 调用失败。\n";
      return 1;
      }
      std::cout << "电源来源: " << AcLineStatusStr(sps.ACLineStatus) << "\n";
      if (sps.BatteryLifePercent != 255)
      std::cout << "剩余百分比: " << (int)sps.BatteryLifePercent << "%\n";
      else
      std::cout << "剩余百分比: 未知\n";
      if (sps.BatteryLifeTime != (DWORD)-1)
      std::cout << "估算剩余时间: " << sps.BatteryLifeTime / 60 << " 分钟\n";
      else
      std::cout << "估算剩余时间: 未知\n";
      if (sps.BatteryFullLifeTime != (DWORD)-1)
      std::cout << "满电理论时间: " << sps.BatteryFullLifeTime / 60 << " 分钟\n";
      else
      std::cout << "满电理论时间: 未知\n";
      std::cout << "BatteryFlag 原始值: 0x" << std::hex << (int)sps.BatteryFlag << std::dec << "\n";
      return 0;
      }
#include <windows.h>
  #include <iostream>
    #include <string>
      static const GUID GUID_BATTERY_PERCENTAGE_REMAINING =
      { 0xA7AD8041, 0xB45A, 0x4CAE, {0x87,0xA3,0xEE,0xEC,0xBB,0xEE,0xB1,0x0} };
      HPOWERNOTIFY gNotify = nullptr;
      LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
      switch (msg) {
      case WM_POWERBROADCAST:
      if (wParam == PBT_POWERSETTINGCHANGE) {
      POWERBROADCAST_SETTING* p = (POWERBROADCAST_SETTING*)lParam;
      if (IsEqualGUID(p->PowerSetting, GUID_BATTERY_PERCENTAGE_REMAINING) &&
      p->DataLength == sizeof(DWORD)) {
      DWORD pct = *(DWORD*)p->Data;
      std::cout << "[事件] 电池百分比变化: " << pct << "%\n";
      }
      }
      break;
      case WM_DESTROY:
      if (gNotify) UnregisterPowerSettingNotification(gNotify);
      PostQuitMessage(0);
      break;
      }
      return DefWindowProc(hWnd, msg, wParam, lParam);
      }
      int main() {
      WNDCLASS wc{};
      wc.lpfnWndProc = WndProc;
      wc.lpszClassName = L"BatteryNotifyWindow";
      RegisterClass(&wc);
      HWND hWnd = CreateWindow(wc.lpszClassName, L"BatteryNotify", 0,
      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
      nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
      if (!hWnd) {
      std::cerr << "窗口创建失败\n";
      return 1;
      }
      gNotify = RegisterPowerSettingNotification(hWnd, &GUID_BATTERY_PERCENTAGE_REMAINING, DEVICE_NOTIFY_WINDOW_HANDLE);
      if (!gNotify) {
      std::cerr << "注册电池百分比通知失败\n";
      return 1;
      }
      std::cout << "开始消息循环,调整系统电池状态可触发事件...\n";
      MSG msg;
      while (GetMessage(&msg, nullptr, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
      }
      return 0;
      }
#include <windows.h>
  #include <comdef.h>
    #include <Wbemidl.h>
      #include <iostream>
        #pragma comment(lib, "wbemuuid.lib")
        int main() {
        HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
        if (FAILED(hr)) {
        std::cerr << "CoInitializeEx 失败\n";
        return 1;
        }
        hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
        RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL);
        if (FAILED(hr)) {
        std::cerr << "CoInitializeSecurity 失败 0x" << std::hex << hr << "\n";
        CoUninitialize();
        return 1;
        }
        IWbemLocator* pLoc = nullptr;
        hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
        IID_IWbemLocator, (LPVOID*)&pLoc);
        if (FAILED(hr)) {
        std::cerr << "创建 IWbemLocator 失败\n";
        CoUninitialize();
        return 1;
        }
        IWbemServices* pSvc = nullptr;
        hr = pLoc->ConnectServer(
        _bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0,
        NULL, 0, 0, &pSvc);
        if (FAILED(hr)) {
        std::cerr << "连接 WMI 服务失败\n";
        pLoc->Release();
        CoUninitialize();
        return 1;
        }
        hr = CoSetProxyBlanket(pSvc,
        RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
        RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL, EOAC_NONE);
        if (FAILED(hr)) {
        std::cerr << "CoSetProxyBlanket 失败\n";
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return 1;
        }
        IEnumWbemClassObject* pEnumerator = nullptr;
        hr = pSvc->ExecQuery(
        bstr_t("WQL"),
        bstr_t("SELECT * FROM Win32_Battery"),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        NULL,
        &pEnumerator);
        if (FAILED(hr)) {
        std::cerr << "WMI 查询失败或无电池\n";
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return 1;
        }
        IWbemClassObject* pObj = nullptr;
        ULONG ret = 0;
        while (pEnumerator) {
        HRESULT rh = pEnumerator->Next(WBEM_INFINITE, 1, &pObj, &ret);
        if (0 == ret) break;
        VARIANT vt;
        VariantInit(&vt);
        if (SUCCEEDED(pObj->Get(L"EstimatedChargeRemaining", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        std::cout << "剩余百分比: " << vt.intVal << "%\n";
        VariantClear(&vt);
        if (SUCCEEDED(pObj->Get(L"DesignCapacity", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        std::cout << "设计容量: " << vt.intVal << " mWh\n";
        VariantClear(&vt);
        if (SUCCEEDED(pObj->Get(L"FullChargeCapacity", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        std::cout << "满充容量: " << vt.intVal << " mWh\n";
        VariantClear(&vt);
        if (SUCCEEDED(pObj->Get(L"BatteryStatus", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        std::cout << "状态代码: " << vt.intVal << "\n";
        VariantClear(&vt);
        pObj->Release();
        }
        pSvc->Release();
        pLoc->Release();
        pEnumerator->Release();
        CoUninitialize();
        return 0;
        }
#include <windows.h>
  #include <vector>
    #include <numeric>
      #include <iostream>
        struct Sample {
        DWORD secondsRemaining; // 原始估算秒数 (-1 表未知)
        ULONGLONG timestamp;
        };
        // 简单指数移动平均 (EMA)
        double smoothEMA(double prev, double current, double alpha) {
        if (prev < 0) return current;
        return prev * (1.0 - alpha) + current * alpha;
        }
        int main() {
        SYSTEM_POWER_STATUS sps{};
        double emaMinutes = -1.0;
        std::vector<int> historyPercent;
          for (int i = 0; i < 10; ++i) {
          if (!GetSystemPowerStatus(&sps)) {
          std::cerr << "获取失败\n";
          break;
          }
          if (sps.BatteryLifePercent != 255) {
          historyPercent.push_back(sps.BatteryLifePercent);
          std::cout << "当前百分比: " << (int)sps.BatteryLifePercent << "%\n";
          } else {
          std::cout << "百分比未知\n";
          }
          if (sps.BatteryLifeTime != (DWORD)-1) {
          double minutes = sps.BatteryLifeTime / 60.0;
          emaMinutes = smoothEMA(emaMinutes, minutes, 0.3);
          std::cout << "原始估算剩余: " << minutes << " 分钟, 平滑后: " << emaMinutes << " 分钟\n";
          } else {
          std::cout << "剩余时间未知\n";
          }
          Sleep(1500); // 模拟采样
          }
          if (!historyPercent.empty()) {
          double avg = std::accumulate(historyPercent.begin(), historyPercent.end(), 0.0) / historyPercent.size();
          std::cout << "平均百分比(测试阶段): " << avg << "%\n";
          }
          return 0;
          }
// 需要在项目中启用 C++/WinRT,并链接 windowsapp
// vcpkg 或 VS 安装 Windows SDK 后可用。
// clang-format off
#include <winrt/Windows.Devices.Power.h>
  #include <winrt/Windows.System.Power.h>
    #include <iostream>
      int main() {
      winrt::init_apartment();
      auto battery = winrt::Windows::Devices::Power::Battery::AggregateBattery();
      auto report = battery.GetReport();
      auto rem = report.RemainingCapacityInMilliwattHours();
      auto full = report.FullChargeCapacityInMilliwattHours();
      auto design = report.DesignCapacityInMilliwattHours();
      auto status = report.Status();
      if (rem) std::cout << "剩余容量: " << rem.Value() << " mWh\n";
      else     std::cout << "剩余容量未知\n";
      if (full) std::cout << "满充容量: " << full.Value() << " mWh\n";
      else      std::cout << "满充容量未知\n";
      if (design) std::cout << "设计容量: " << design.Value() << " mWh\n";
      else        std::cout << "设计容量未知\n";
      if (rem && full && full.Value() > 0) {
      double pct = rem.Value() * 100.0 / full.Value();
      std::cout << "估算百分比: " << pct << "%\n";
      }
      if (design && full && design.Value() > 0) {
      double wear = (1.0 - (double)full.Value() / design.Value()) * 100.0;
      std::cout << "磨损率: " << wear << "%\n";
      }
      auto duration = report.EstimatedRemainingDuration();
      if (duration) {
      auto minutes = duration.Value().count() / 10000000.0 / 60.0; // TimeSpan 的 tick = 100ns
      std::cout << "估算剩余时间: " << minutes << " 分钟\n";
      } else {
      std::cout << "估算剩余时间: 未知\n";
      }
      auto saver = winrt::Windows::System::Power::PowerManager::EnergySaverStatus();
      std::cout << "节电模式状态: " << (int)saver << " (0=Disabled,1=Off,2=On)\n";
      return 0;
      }
#include <windows.h>
  #include <iostream>
    // 使用服务句柄 (这里用控制台模拟) 注册电源设置变更通知
    // 实际服务中常通过 SERVICE_STATUS_HANDLE 等处理。
    static const GUID GUID_ACDC_POWER_SOURCE =
    { 0x5D3E9A59, 0xE9D5, 0x4B00,{0xA6,0xBD,0xFF,0x34,0xFF,0x51,0x65,0x48} };
    int main() {
    HANDLE hProc = GetCurrentProcess();
    HPOWERNOTIFY hNotify = RegisterPowerSettingNotification(hProc, &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_SERVICE_HANDLE);
    if (!hNotify) {
    std::cerr << "注册失败\n";
    return 1;
    }
    std::cout << "模拟服务等待。按 Ctrl+C 退出。\n";
    // 简单循环,实际服务应使用 HandlerEx 或服务控制回调
    while (true) {
    Sleep(5000);
    // 在服务模式下,电源设置通知通过 ServiceMain 中的回调到来(需配合 SERVICE_CONTROL_POWEREVENT)
    // 此处仅示意;真正服务需创建 Service 并处理 SERVICE_CONTROL_POWEREVENT 事件。
    }
    UnregisterPowerSettingNotification(hNotify);
    return 0;
    }
#include <windows.h>
  #include <comdef.h>
    #include <Wbemidl.h>
      #include <iostream>
        #pragma comment(lib, "wbemuuid.lib")
        struct BatterySnapshot {
        bool hasWin32 = false;
        int percent = -1;
        int design = -1;
        int full = -1;
        int remainingMWh = -1;
        };
        bool QueryWin32(BatterySnapshot& snap) {
        SYSTEM_POWER_STATUS sps{};
        if (!GetSystemPowerStatus(&sps)) return false;
        snap.hasWin32 = true;
        if (sps.BatteryLifePercent != 255) snap.percent = sps.BatteryLifePercent;
        return true;
        }
        bool QueryWMI(BatterySnapshot& snap) {
        HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
        if (FAILED(hr)) return false;
        hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
        RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL);
        if (FAILED(hr)) { CoUninitialize(); return false; }
        IWbemLocator* pLoc = nullptr;
        hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
        IID_IWbemLocator, (LPVOID*)&pLoc);
        if (FAILED(hr)) { CoUninitialize(); return false; }
        IWbemServices* pSvc = nullptr;
        hr = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
        if (FAILED(hr)) { pLoc->Release(); CoUninitialize(); return false; }
        hr = CoSetProxyBlanket(pSvc,
        RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
        RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL, EOAC_NONE);
        if (FAILED(hr)) {
        pSvc->Release(); pLoc->Release(); CoUninitialize();
        return false;
        }
        IEnumWbemClassObject* pEnumerator = nullptr;
        hr = pSvc->ExecQuery(bstr_t("WQL"), bstr_t("SELECT * FROM Win32_Battery"),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
        if (FAILED(hr)) {
        pSvc->Release(); pLoc->Release(); CoUninitialize();
        return false;
        }
        IWbemClassObject* pObj = nullptr;
        ULONG ret = 0;
        bool found = false;
        while (pEnumerator) {
        HRESULT rh = pEnumerator->Next(WBEM_INFINITE, 1, &pObj, &ret);
        if (0 == ret) break;
        found = true;
        VARIANT vt;
        VariantInit(&vt);
        if (SUCCEEDED(pObj->Get(L"EstimatedChargeRemaining", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        snap.percent = vt.intVal;
        VariantClear(&vt);
        if (SUCCEEDED(pObj->Get(L"DesignCapacity", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        snap.design = vt.intVal;
        VariantClear(&vt);
        if (SUCCEEDED(pObj->Get(L"FullChargeCapacity", 0, &vt, 0, 0)) && vt.vt != VT_NULL)
        snap.full = vt.intVal;
        VariantClear(&vt);
        pObj->Release();
        }
        if (pEnumerator) pEnumerator->Release();
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return found;
        }
        int main() {
        BatterySnapshot snap;
        QueryWin32(snap);
        QueryWMI(snap);
        std::cout << "综合结果:\n";
        if (snap.percent >= 0) std::cout << "剩余百分比: " << snap.percent << "%\n";
        else std::cout << "剩余百分比不可用\n";
        if (snap.design > 0) std::cout << "设计容量: " << snap.design << " mWh\n";
        else std::cout << "设计容量未知\n";
        if (snap.full > 0) std::cout << "满充容量: " << snap.full << " mWh\n";
        else std::cout << "满充容量未知\n";
        if (snap.design > 0 && snap.full > 0) {
        double wear = (1.0 - (double)snap.full / snap.design) * 100.0;
        std::cout << "磨损率: " << wear << "%\n";
        } else {
        std::cout << "无法计算磨损率\n";
        }
        return 0;
        }

16. 策略整合与最佳实践

  1. 初始化阶段:
    • 首先尝试 WinRT 获取容量(更丰富),失败回退至 WMI + Win32。
  2. 显示逻辑:
    • 百分比优先来源:WinRT 剩余容量 ÷ 满充;否则 Win32 BatteryLifePercent;再否则 WMI EstimatedChargeRemaining
  3. 事件刷新:
    • 注册 GUID_BATTERY_PERCENTAGE_REMAINING 更新百分比。
    • 注册 GUID_ACDC_POWER_SOURCE 切换电源来源时立即更新 UI。
  4. 磨损监控:
    • 每次启动记录 FullChargeCapacity。定期判断变化趋势。
  5. 性能:
    • 事件触发 vs 30 秒轮询设计容量(容量不常变)。
  6. 冗余与缓存:
    • 最近一次成功数据缓存到内存,防止接口临时失败导致 UI 闪烁。
  7. 错误分级:
    • 无电池:直接显示“设备为台式机或未检测到电池”。
    • 部分字段缺失:字段旁显示“(驱动未提供)”标签。

17. 常见问题解析

问题说明建议
WinRT 某些字段 null驱动不支持或权限环境提示用户或回退 WMI
WMI 返回空集合无电池或驱动不暴露结合 Win32 判断 ACLineStatus
电池百分比突然跳跃快速负载变化 / 系统重新校准平滑显示 + 增量阈值刷新
剩余时间长为 -1无放电速率支持隐藏时间改为显示“% + 使用模式”
磨损率异常高或突变校准/BIOS 更新/温度导致多次趋势确认再提示用户
多语言环境数值格式问题本地化输出需要注意使用标准数值再格式化本地语言

18. 安全与权限

  • GetSystemPowerStatus/WinRT Battery 基本无需管理员权限。
  • WMI 查询同样通常不需管理员(除远程或策略限制)。
  • ETW 深度跟踪可能需管理员。
  • 不应修改系统电源方案注册表(用 powercfg /setactive 更安全)。

19. 进一步扩展方向

  • 添加历史曲线(容量 vs 时间)→ JSON/SQLite 存档。
  • 增加对 PowerManager::EnergySaverStatus 变化事件响应(节电模式下降低刷新频)。
  • 集成 powercfg /batteryreport 定期解析 HTML 抽取统计(需 HTML 解析库)。
  • (高级)使用 ETW 统计高耗电进程,对应用进行能耗提示。

20. 总结

Windows 11 笔记本电池接口呈多层结构,开发者可根据需求选用:

  • 快速:GetSystemPowerStatus
  • 完整容量与事件:C++/WinRT Battery + ReportUpdated
  • 运维与脚本:WMI (Win32_Battery)
  • 实时事件:RegisterPowerSettingNotification + GUID
  • 历史分析:powercfg /batteryreport
  • 高级平台信息:CallNtPowerInformation

推荐组合策略:

  1. 初始化(WinRT → WMI → Win32 回退)。
  2. 事件驱动刷新(减少轮询)。
  3. 平滑算法处理跳动值。
  4. 记录并计算磨损率趋势。
  5. 兼容空值与多电池情况。

通过本文示例与策略,可以构建一个鲁棒、高性能、用户体验友好的电池信息模块 / 电源管理辅助工具。


posted @ 2026-01-24 14:22  yangykaifa  阅读(4)  评论(0)    收藏  举报