在 Windows 系统中,获取显示器信息(如制造商、序列号和产品代码)是一项常见任务。本文将展示如何使用 C++ 通过 Windows Management Instrumentation (WMI) 和 Component Object Model (COM) 接口实现这一功能。我们将以 WmiMonitorID 类为例,逐步构建一个健壮的程序,并分享实现过程中的关键注意事项。
显示器信息通常存储在硬件的 EDID (Extended Display Identification Data) 中,可以通过操作系统接口访问。WMI 提供了一个高层次的查询机制,而 C++ 通过 COM 接口直接与之交互,相较于 Python 等语言,C++ 提供了更高的性能和控制力,但也带来了额外的复杂性。
我们的目标是从 WmiMonitorID 类获取以下字段:
以下是实现的核心步骤:
COM 是 Windows 的组件对象模型,WMI 依赖它运行。我们需要:
通过 IWbemLocator 和 IWbemServices 接口连接到 WMI 的 "root\WMI" 命名空间。
使用 WQL (WMI Query Language) 查询 WmiMonitorID 类,获取显示器数据。
WMI 返回的数据存储在 SAFEARRAY 中,类型为 VT_ARRAY | VT_I4,需提取低字节并转换为字符串。
通过 EnumDisplayDevices 获取附加信息(如设备名称和驱动描述)。
释放 COM 对象和内存,确保程序无泄漏。
以下是优化后的 C++ 代码,获取并显示完整的显示器信息:
#include
#include
#include
#include
#include
#include
#include // 用于十六进制格式化
#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
struct MonitorInfo {
std::string Manufacturer;
std::string SerialNumber;
std::string ProductCode;
std::string DeviceName;
std::string DeviceString;
DWORD StateFlags;
};
// Helper function to extract string from SAFEARRAY (VT_I4)
std::string extractStringFromSafeArray(SAFEARRAY* sa, bool hexOutput = false) {
std::string result;
if (!sa) return result;
long lBound, uBound;
SafeArrayGetLBound(sa, 1, &lBound);
SafeArrayGetUBound(sa, 1, &uBound);
INT* data;
if (SUCCEEDED(SafeArrayAccessData(sa, (void**)&data))) {
if (hexOutput) {
// 只取第一个非零字节并转为十六进制
for (long i = lBound; i <= uBound; ++i) {
BYTE byteValue = static_cast(data[i] & 0xFF);
if (byteValue != 0) {
std::ostringstream oss;
oss << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(byteValue);
result = oss.str();
break; // 只取第一个字节
}
}
} else {
// 提取所有非零字节作为字符串
for (long i = lBound; i <= uBound; ++i) {
BYTE byteValue = static_cast(data[i] & 0xFF);
if (byteValue != 0 && byteValue < 128) {
result += static_cast(byteValue);
}
}
// Trim trailing spaces
while (!result.empty() && result.back() == ' ') {
result.pop_back();
}
}
SafeArrayUnaccessData(sa);
}
return result;
}
std::vector getMonitorInfo() {
std::vector monitorList;
HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres)) return monitorList;
hres = CoInitializeSecurity(
nullptr, -1, nullptr, nullptr,
RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr, EOAC_NONE, nullptr
);
if (FAILED(hres)) {
CoUninitialize();
return monitorList;
}
IWbemLocator* pLoc = nullptr;
hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
if (FAILED(hres)) {
CoUninitialize();
return monitorList;
}
IWbemServices* pSvc = nullptr;
BSTR wmiNamespace = SysAllocString(L"ROOT\\WMI");
hres = pLoc->ConnectServer(wmiNamespace, nullptr, nullptr, nullptr, 0, nullptr, nullptr, &pSvc);
SysFreeString(wmiNamespace);
if (FAILED(hres)) {
pLoc->Release();
CoUninitialize();
return monitorList;
}
hres = CoSetProxyBlanket(
pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr, EOAC_NONE
);
if (FAILED(hres)) {
pSvc->Release();
pLoc->Release();
CoUninitialize();
return monitorList;
}
IEnumWbemClassObject* pEnumerator = nullptr;
BSTR queryLanguage = SysAllocString(L"WQL");
BSTR queryString = SysAllocString(L"SELECT * FROM WmiMonitorID");
hres = pSvc->ExecQuery(queryLanguage, queryString, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, &pEnumerator);
SysFreeString(queryLanguage);
SysFreeString(queryString);
if (FAILED(hres)) {
pSvc->Release();
pLoc->Release();
CoUninitialize();
return monitorList;
}
IWbemClassObject* pclsObj = nullptr;
ULONG uReturn = 0;
while (pEnumerator) {
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (0 == uReturn) break;
VARIANT vtProp;
std::string manufacturer;
hr = pclsObj->Get(L"ManufacturerName", 0, &vtProp, 0, 0);
if (SUCCEEDED(hr)) manufacturer = extractStringFromSafeArray(vtProp.parray);
VariantClear(&vtProp);
std::string serial;
hr = pclsObj->Get(L"SerialNumberID", 0, &vtProp, 0, 0);
if (SUCCEEDED(hr)) serial = extractStringFromSafeArray(vtProp.parray);
VariantClear(&vtProp);
std::string productCode;
hr = pclsObj->Get(L"ProductCodeID", 0, &vtProp, 0, 0);
if (SUCCEEDED(hr)) productCode = extractStringFromSafeArray(vtProp.parray, true); // 十六进制
VariantClear(&vtProp);
monitorList.push_back({ manufacturer, serial, productCode, "", "", 0 });
pclsObj->Release();
}
pEnumerator->Release();
pSvc->Release();
pLoc->Release();
CoUninitialize();
std::vector displayDevices;
DWORD i = 0;
DISPLAY_DEVICE dd = { 0 };
dd.cb = sizeof(dd);
while (EnumDisplayDevices(nullptr, i, &dd, 0)) {
MonitorInfo info;
info.DeviceName = dd.DeviceName;
info.DeviceString = dd.DeviceString;
info.StateFlags = dd.StateFlags;
displayDevices.push_back(info);
i++;
}
for (size_t i = 0; i < monitorList.size() && i < displayDevices.size(); ++i) {
monitorList[i].DeviceName = displayDevices[i].DeviceName;
monitorList[i].DeviceString = displayDevices[i].DeviceString;
monitorList[i].StateFlags = displayDevices[i].StateFlags;
}
return monitorList;
}
int main() {
std::vector monitors = getMonitorInfo();
if (!monitors.empty()) {
for (size_t i = 0; i < monitors.size(); ++i) {
std::cout << "Monitor " << (i + 1) << ":\n";
std::cout << " Manufacturer: " << monitors[i].Manufacturer << "\n";
std::cout << " SerialNumber: " << monitors[i].SerialNumber << "\n";
std::cout << " ProductCode: " << monitors[i].ProductCode << "\n";
std::cout << " DeviceName: " << monitors[i].DeviceName << "\n";
std::cout << " DeviceString: " << monitors[i].DeviceString << "\n";
std::cout << " StateFlags: " << monitors[i].StateFlags << "\n";
std::cout << std::string(40, '-') << "\n";
}
} else {
std::cout << "No monitors found.\n";
}
return 0;
}
编译方法:
g++ example.cpp -lole32 -loleaut32 -lwbemuuid -o example.exe
执行:
.\example.exe
显示显示器相关参数,测试代码是否正常:
Get-WmiObject -Namespace "root\WMI" -Class WmiMonitorID
显示指定参数
Get-WmiObject -Namespace "root\WMI" -Class WmiMonitorID | Format-List ManufacturerName, SerialNumberID, ProductCodeID
指定参数输出结果:
ManufacturerName : {76, 69, 67, 0...}
SerialNumberID : {71, 75, 49, 67...}
ProductCodeID : {50, 55, 56, 50...}
代码输出结果:
Monitor 1:
Manufacturer: LEC
SerialNumber: GK1CKYP3
ProductCode: 0x32 -->ProductCodeID :50
DeviceName: \\.\DISPLAY1
DeviceString: Intel(R) UHD Graphics 630
StateFlags: 5
----------------------------------------
在实现过程中,我遇到了一些挑战,以下是关键注意事项:
相比 Python(通过 wmi 模块),C++ 的实现:
import wmi
import win32api
# 使用 WMI 获取显示器的 EDID 数据
w = wmi.WMI(namespace="root\\WMI")
for monitor in w.WmiMonitorID():
manufacturer = "".join(chr(c) for c in monitor.ManufacturerName if c > 0)
serial = "".join(chr(c) for c in monitor.SerialNumberID if c > 0)
product_code = monitor.ProductCodeID
monitor_list.append({
"Manufacturer": manufacturer.strip(),
"SerialNumber": serial.strip(),
"ProductCode": hex(product_code[0]) if isinstance(product_code, tuple) and len(product_code) > 0 else None
})
通过 C++ 和 WMI 获取显示器信息是一次深入 Windows 系统编程的实践。掌握 COM 接口和 SAFEARRAY 解析是关键,合理的错误处理和资源管理能确保程序健壮性。如果你需要更灵活的输出格式(如十六进制 "0x32"),只需调整 extractStringFromSafeArray 的逻辑。
希望这篇博客对你理解 C++ 系统编程有所帮助!有问题或改进建议,欢迎留言讨论。