Windows 平台实现细节¶
Windows 下的 CPU 信息主要通过 WMI(Windows Management Instrumentation)和 CPUID 指令获取。WMI 能拿到大部分基础信息,但查询开销较大且需要小心处理 COM 生命周期。CPU 指令则用于特性检测,这是跨平台的一致方案。
COM 初始化¶
WMI 查询必须在 COM 环境中进行,我们用 COMHelper 封装了初始化和清理逻辑。选择 MTA(多线程公寓)而不是 STA,是因为 CPU 查询可能在不同线程执行。
cf::expected<void, CPUInfoErrorType> query_cpu_basic_info(cf::CPUInfoHost& hostInfo) {
return cf::COMHelper<void, CPUInfoErrorType>::RunComInterfacesMTA(
[&hostInfo]() -> cf::expected<void, CPUInfoErrorType> {
// WMI 查询代码
});
}
这个封装确保 COM 正确初始化,线程退出时自动调用 CoUninitialize(),而且异常安全。
基础信息¶
CPU 型号、厂商、架构这些信息从 Win32_Processor 类查询。WQL 查询语法类似 SQL,但只能用于查询系统信息。
cf::expected<void, CPUInfoErrorType> query_cpu_basic_info(cf::CPUInfoHost& hostInfo) {
IWbemLocator* pLoc = nullptr;
IWbemServices* pSvc = nullptr;
// 1. 创建 WMI 定位器
CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID*)&pLoc);
// 2. 连接到 WMI 服务
pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), nullptr, nullptr,
nullptr, 0, nullptr, nullptr, &pSvc);
// 3. 设置代理安全级别(必须,否则会访问被拒)
CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
nullptr, RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE);
// 4. 查询 CPU 信息
auto modelName = queryWMIProperty(pSvc, L"Win32_Processor", L"Name");
if (modelName) hostInfo.model = *modelName;
auto manufacturer = queryWMIProperty(pSvc, L"Win32_Processor", L"Manufacturer");
if (manufacturer) hostInfo.manufest = *manufacturer;
// 清理
pSvc->Release();
pLoc->Release();
return {};
}
⚠️ CoSetProxyBlanket() 调用是必须的,否则查询会返回 E_ACCESSDENIED。这个坑踩过一次。
架构值转换¶
WMI 返回的 Architecture 是个数字,需要映射到字符串。Windows 的架构值定义有点奇怪——9 是 x64,12 是 ARM64——但这些都是标准值,照着映射就行。
std::string architectureToString(UINT16 archValue) {
switch (archValue) {
case 0: return "x86";
case 1: return "MIPS";
case 2: return "Alpha";
case 3: return "PowerPC";
case 5: return "ARM";
case 6: return "ia64";
case 9: return "x64";
case 12: return "ARM64";
default: return "Unknown";
}
}
性能信息¶
核心数和最大频率可以从 Win32_Processor 直接拿到,但当前频率和 CPU 使用率需要其他方式。当前频率 WMI 也提供,但不太可靠;使用率则用 PDH(Performance Data Helper)API。
float get_cpu_usage() {
PDH_HQUERY query;
PDH_HCOUNTER counter;
PdhOpenQuery(nullptr, 0, &query);
PdhAddCounter(query, L"\\Processor(_Total)\\% Processor Time", 0, &counter);
// 第一次调用初始化计数器
PdhCollectQueryData(query);
Sleep(1000); // 必须等待,否则数据无效
PdhCollectQueryData(query);
PDH_FMT_COUNTERVALUE value;
PdhGetFormattedCounterValue(counter, PDH_FMT_DOUBLE, nullptr, &value);
PdhCloseQuery(query);
return static_cast<float>(value.doubleValue);
}
⚠️ 两次 PdhCollectQueryData() 之间必须有延迟,否则返回的数据是 0 或无效值。这是因为性能计数器是基于时间差计算的。
特性检测¶
CPU 特性通过 CPUID 指令检测,这是 x86 平台的标准方式。MSVC 用 __cpuid() intrinsic,GCC/Clang 用内联汇编。
void cpuid(int info[4], int function_id) {
#ifdef _MSC_VER
__cpuid(info, function_id);
#else
__asm__ (
"cpuid"
: "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3])
: "a"(function_id)
);
#endif
}
bool detect_feature(const char* feature_name) {
int info[4];
cpuid(info, 1); // EAX=1 返回基本特性
if (strcmp(feature_name, "SSE") == 0)
return (info[3] & (1 << 25)) != 0;
if (strcmp(feature_name, "SSE2") == 0)
return (info[3] & (1 << 26)) != 0;
if (strcmp(feature_name, "AVX") == 0)
return (info[2] & (1 << 28)) != 0;
if (strcmp(feature_name, "AVX2") == 0) {
cpuid(info, 7); // EAX=7 返回扩展特性
return (info[1] & (1 << 5)) != 0;
}
return false;
}
EAX=1 返回的是基本特性,EAX=7 返回的是扩展特性。不同特性位分布在不同的寄存器里,需要查 Intel 的手册确认。
温度信息¶
Windows 上温度信息比较麻烦。MSAcpi_ThermalZoneTemperature WMI 类理论上可以查,但大部分 PC 不支持。注册表 HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\ThermalInfo 也经常是空的。
std::optional<uint16_t> get_cpu_temperature() {
auto temp = queryWMIProperty(L"MSAcpi_ThermalZoneTemperature", L"CurrentTemperature");
if (temp) {
// 返回值是摄氏度 * 10 - 273.15(开尔文转摄氏度)
return (std::stoull(*temp) / 10.0) - 273.15;
}
return std::nullopt; // 大多数情况不可用
}
所以我们的实现里,温度信息在 Windows 上基本总是 std::nullopt。这不是 bug,是 Windows 硬件生态的限制。
资源管理¶
WMI 和 COM 的资源管理很繁琐,每个接口都要手动 Release()。用 ScopeGuard 可以确保即使中途出错也不会泄漏。
IWbemLocator* pLoc = nullptr;
IWbemServices* pSvc = nullptr;
IWbemClassObject* pclsObj = nullptr;
VARIANT vtProp;
cf::ScopeGuard locGuard([&pLoc]() { if (pLoc) pLoc->Release(); });
cf::ScopeGuard svcGuard([&pSvc]() { if (pSvc) pSvc->Release(); });
cf::ScopeGuard objGuard([&pclsObj]() { if (pclsObj) pclsObj->Release(); });
cf::ScopeGuard varGuard([&vtProp]() { VariantClear(&vtProp); });
// ... 复杂的查询逻辑,无论哪里返回,资源都会被释放