必须调用CoInitializeEx(nullptr, COINIT_MULTITHREADED)和CoInitializeSecurity,再用WQL查询Win32_PhysicalMedia的SerialNumber,通过_bstr_t安全获取字符串,按pclsObj→pEnumerator→pSvc→pLoc顺序释放。

WMI 查询硬盘序列号必须用 COM 初始化
不调用 CoInitializeEx 和 CoInitializeSecurity,后续所有 WMI 调用都会失败,返回 RPC_E_CHANGED_MODE 或 WBEM_E_ACCESS_DENIED。这是最常卡住的地方,不是权限问题,而是 COM 线程模型没配对。
必须用 COINIT_MULTITHREADED(不能用 COINIT_APARTMENTTHREADED),因为 WMI 的默认提供者是多线程安全的,且 IEnumWbemClassObject::Next 在单线程套间下可能无限等待。
- 调用
CoInitializeEx(nullptr, COINIT_MULTITHREADED)为当前线程设置多线程模型 - 紧接着调用
CoInitializeSecurity,至少传入nullptr、-1、nullptr、nullptr、RPC_C_AUTHN_LEVEL_DEFAULT、RPC_C_IMP_LEVEL_IMPERSONATE、nullptr、EOAC_NONE、nullptr - 漏掉
CoSetProxyBlanket也可能导致某些系统(如 Windows Server)返回WBEM_E_ACCESS_DENIED,建议对pSvc补上该调用
WQL 查询 Win32_PhysicalMedia 是最可靠路径
别用 Win32_DiskDrive 的 SerialNumber 属性——它在多数 SATA/NVMe 硬盘上为空;也别依赖 Win32_BIOS.SerialNumber,那是主板的。
Win32_PhysicalMedia 对应物理介质本身,其 SerialNumber 字段由固件直接提供,兼容性好,NVMe、SATA、USB 移动硬盘基本都支持(少数 USB 桥芯片会返回空,属硬件限制)。
立即学习“C++免费学习笔记(深入)”;
- WQL 语句写成
"SELECT SerialNumber FROM Win32_PhysicalMedia",不要加WHERE条件——不同硬盘可能没有Tag或Name与Win32_DiskDrive对齐 - 如果只想查某块盘,得先用
Win32_DiskDrive获取PNPDeviceID,再关联Win32_DiskDriveToDiskPartition和Win32_DiskPartition,最终通过Win32_DiskPartitionToDiskDrive反查,太重;直接枚举Win32_PhysicalMedia更轻量 - 注意:部分虚拟机(如 VirtualBox)返回的是模拟串号,Hyper-V 默认关闭该字段,需在 VM 设置中启用 “Enable serial number”
获取字符串值必须用 bstr_t 或 SysAllocStringLen
WMI 返回的 VARIANT 中,SerialNumber 是 VT_BSTR 类型,但直接用 wcscpy 或强制转 wchar_t* 会崩溃或乱码——因为 BSTR 前有长度头,且内存由 COM 分配,不能用 delete[] 或 free 释放。
- 推荐用 ATL 的
_bstr_t:声明_bstr_t serial = var.bstrVal;,之后可直接用(const wchar_t*)serial安全取值 - 不用 ATL?手动处理:检查
var.vt == VT_BSTR,然后用std::wstring(serial.bstrVal ? serial.bstrVal : L"")构造,绝不能 对var.bstrVal调用SysFreeString—— WMI 接口已管理其生命周期 - 如果
var.vt == VT_NULL或VT_EMPTY,说明该设备未报告序列号,跳过即可,不要解引用var.bstrVal
Release 顺序错一个就内存泄漏或 crash
WMI 对象链是:IWbemLocator → IWbemServices → IEnumWbemClassObject → IWbemClassObject。COM 规则要求后创建的先 Release,且每个 Release() 必须对应一次 AddRef()(WMI 接口内部已做)。
- 典型错误:在循环里对每个
pclsObj调用Release()后,又对pEnumerator调用Release(),但忘了pSvc和pLoc的释放 - 正确顺序(按作用域倒序):
pclsObj→pEnumerator→pSvc→pLoc,最后才是CoUninitialize() - 更安全的做法:用
ATL::CComPtr包裹所有接口指针,构造时Attach,析构自动Release,避免裸指针遗漏
实际跑通的关键不在代码长短,而在 COM 生命周期和 WMI 数据类型的匹配精度——错一点,不是查不到,就是程序退出时崩在 CoUninitialize 里。



















