【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 中电池数据链路可分三层:
- 硬件层:智能电池(Smart Battery)、电池管理芯片(Fuel Gauge)、主板嵌入式控制器(EC),通过 ACPI 表(如
_BIF、_BST)提供信息:设计容量(Design Capacity)、满充容量(Last Full Charge / FullChargeCapacity)、剩余容量、充/放电速率、状态标志。 - 驱动层:ACPI 驱动(
Acpi.sys)+ 电池类驱动(Battery.sys)统一抽象,内核电源管理器(Power Manager)维护当前电源来源(交流/电池)、剩余电量百分比、估算剩余可用时间等。 - 上层接口: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 Runtime | Battery.AggregateBattery.GetReport() | 容量(设计/满充/剩余)、充放电速率、估算剩余时间 | 现代 C++/WinRT 或 UWP/WinUI 桌面 |
| WMI | 类 Win32_Battery | 设计容量、满充容量、剩余电量、状态码 | 运维脚本/批量采集 |
| 命令行 | powercfg /batteryreport | 历史充放电、容量漂移 | 深度分析、报表生成 |
| 事件 GUID | GUID_BATTERY_PERCENTAGE_REMAINING 等 | 细粒度变化通知 | 节能策略、动态 UI |
| ETW | Power 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_POWERBROADCAST,wParam == PBT_POWERSETTINGCHANGE,lParam 指向 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,包含:
RemainingCapacityInMilliwattHoursFullChargeCapacityInMilliwattHoursDesignCapacityInMilliwattHoursChargeRateInMilliwatts(可能为空)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. 策略整合与最佳实践
- 初始化阶段:
- 首先尝试 WinRT 获取容量(更丰富),失败回退至 WMI + Win32。
- 显示逻辑:
- 百分比优先来源:WinRT 剩余容量 ÷ 满充;否则 Win32
BatteryLifePercent;再否则 WMIEstimatedChargeRemaining。
- 百分比优先来源:WinRT 剩余容量 ÷ 满充;否则 Win32
- 事件刷新:
- 注册
GUID_BATTERY_PERCENTAGE_REMAINING更新百分比。 - 注册
GUID_ACDC_POWER_SOURCE切换电源来源时立即更新 UI。
- 注册
- 磨损监控:
- 每次启动记录
FullChargeCapacity。定期判断变化趋势。
- 每次启动记录
- 性能:
- 事件触发 vs 30 秒轮询设计容量(容量不常变)。
- 冗余与缓存:
- 最近一次成功数据缓存到内存,防止接口临时失败导致 UI 闪烁。
- 错误分级:
- 无电池:直接显示“设备为台式机或未检测到电池”。
- 部分字段缺失:字段旁显示“(驱动未提供)”标签。
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
推荐组合策略:
- 初始化(WinRT → WMI → Win32 回退)。
- 事件驱动刷新(减少轮询)。
- 平滑算法处理跳动值。
- 记录并计算磨损率趋势。
- 兼容空值与多电池情况。
通过本文示例与策略,可以构建一个鲁棒、高性能、用户体验友好的电池信息模块 / 电源管理辅助工具。

浙公网安备 33010602011771号