Collecting Windows Hardware and OS Information in C/C++ using Win32 APIs, Registry, and WMI
Win32/Registry-based approach
Direct Win32 calls and registry queries provide fast access to operating system and hardware metadata. The code below wraps commonly used queries (OS/service pack, CPU, memory, NICs, disks, GPU).
SystemProbe.h
#pragma once
#include <afxtempl.h>
class SystemProbe
{
public:
SystemProbe() {}
~SystemProbe() {}
// OS information
void QueryOSVersion(CString& osVersion, CString& servicePack);
BOOL IsRunningUnderWow64();
// Network interfaces
int EnumerateNics();
void GetNicName(CString& nicName, int index);
// Memory
void QueryMemory(CString& totalPhysical, CString& totalVirtual);
// CPU
void QueryCpu(CString& cpuName, CString& cpuType, DWORD& logicalCount, DWORD& maxClockMHz);
// Disks
void EnumerateDisks(DWORD& outCount, CString outDescriptions[]);
// Graphics adapters
void EnumerateVideoAdapters(DWORD& outCount, CString outNames[]);
private:
CStringList m_nicNames;
CList<DWORD, DWORD&> m_nicBandwidths;
CList<DWORD, DWORD&> m_nicTraffic; // placeholder for future counters
};
SystemProbe.cpp
#include "StdAfx.h"
#include "SystemProbe.h"
#include <float.h>
#include <winperf.h>
static BOOL TryGetNativeSystemInfo(SYSTEM_INFO* p)
{
typedef VOID (WINAPI *PFN)(LPSYSTEM_INFO);
PFN fn = (PFN)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo");
if (fn) { fn(p); return TRUE; }
GetSystemInfo(p); return FALSE;
}
void SystemProbe::QueryOSVersion(CString& osVersion, CString& servicePack)
{
CString buff;
OSVERSIONINFOEX ver = {0};
ver.dwOSVersionInfoSize = sizeof(ver);
if (!GetVersionEx((OSVERSIONINFO*)&ver)) {
OSVERSIONINFO v2 = {0};
v2.dwOSVersionInfoSize = sizeof(v2);
if (!GetVersionEx(&v2)) { osVersion.Empty(); servicePack.Empty(); return; }
ver.dwMajorVersion = v2.dwMajorVersion;
ver.dwMinorVersion = v2.dwMinorVersion;
ver.dwBuildNumber = v2.dwBuildNumber;
lstrcpyn(ver.szCSDVersion, v2.szCSDVersion, _countof(ver.szCSDVersion));
}
SYSTEM_INFO si = {0};
TryGetNativeSystemInfo(&si);
switch (ver.dwPlatformId)
{
case VER_PLATFORM_WIN32_NT:
if (ver.dwMajorVersion == 6 && ver.dwMinorVersion == 0)
{
if (ver.wProductType == VER_NT_WORKSTATION) buff = _T("Windows Vista");
else buff = _T("Windows Server 2008");
}
else if (ver.dwMajorVersion == 5 && ver.dwMinorVersion == 2)
{
if (GetSystemMetrics(SM_SERVERR2)) buff = _T("Windows Server 2003 R2");
else if (ver.wProductType == VER_NT_WORKSTATION && si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
buff = _T("Windows XP Professional x64 Edition");
else buff = _T("Windows Server 2003");
}
else if (ver.dwMajorVersion == 5 && ver.dwMinorVersion == 1) buff = _T("Windows XP");
else if (ver.dwMajorVersion == 5 && ver.dwMinorVersion == 0) buff = _T("Windows 2000");
else if (ver.dwMajorVersion <= 4) buff = _T("Windows NT");
servicePack.Format(_T("%s"), ver.szCSDVersion[0] ? ver.szCSDVersion : _T(""));
osVersion = buff;
break;
case VER_PLATFORM_WIN32_WINDOWS:
if (ver.dwMajorVersion == 4 && ver.dwMinorVersion == 0) {
buff = _T("Windows 95");
if (ver.szCSDVersion[1] == 'B' || ver.szCSDVersion[1] == 'C') buff += _T(" OSR2");
} else if (ver.dwMajorVersion == 4 && ver.dwMinorVersion == 10) {
buff = _T("Windows 98");
if (ver.szCSDVersion[1] == 'A' || ver.szCSDVersion[1] == 'B') buff += _T(" SE");
} else if (ver.dwMajorVersion == 4 && ver.dwMinorVersion == 90) buff = _T("Windows ME");
osVersion = buff; servicePack.Empty();
break;
default:
osVersion.Empty(); servicePack.Empty();
break;
}
}
BOOL SystemProbe::IsRunningUnderWow64()
{
typedef BOOL (WINAPI *PFN)(HANDLE, PBOOL);
PFN pIsWow64 = (PFN)GetProcAddress(GetModuleHandle(_T("kernel32")), "IsWow64Process");
BOOL isWow64 = FALSE;
if (pIsWow64) pIsWow64(GetCurrentProcess(), &isWow64);
return isWow64;
}
void SystemProbe::QueryCpu(CString& cpuName, CString& cpuType, DWORD& logicalCount, DWORD& maxClockMHz)
{
cpuName.Empty(); cpuType.Empty(); logicalCount = 0; maxClockMHz = 0;
// Read CPU marketing name and nominal MHz from registry
CRegKey k;
if (ERROR_SUCCESS == k.Open(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), KEY_READ))
{
WCHAR nameBuf[128] = {0}; DWORD cb = sizeof(nameBuf);
if (ERROR_SUCCESS == k.QueryStringValue(_T("ProcessorNameString"), nameBuf, &cb)) cpuName = nameBuf;
DWORD mhz = 0;
if (ERROR_SUCCESS == k.QueryDWORDValue(_T("~MHz"), mhz)) maxClockMHz = mhz;
k.Close();
}
SYSTEM_INFO si = {0};
GetSystemInfo(&si);
logicalCount = si.dwNumberOfProcessors;
switch (si.dwProcessorType)
{
case PROCESSOR_INTEL_386: cpuType = _T("Intel 386"); break;
case PROCESSOR_INTEL_486: cpuType = _T("Intel 486"); break;
case PROCESSOR_INTEL_PENTIUM: cpuType = _T("Intel Pentium"); break;
case PROCESSOR_INTEL_IA64: cpuType = _T("Intel IA64"); break;
case PROCESSOR_AMD_X8664: cpuType = _T("AMD x86-64"); break;
default: cpuType = _T("Unknown"); break;
}
}
void SystemProbe::QueryMemory(CString& totalPhysical, CString& totalVirtual)
{
MEMORYSTATUS ms = {0};
ms.dwLength = sizeof(ms);
GlobalMemoryStatus(&ms);
DWORD physMB = (DWORD)(ms.dwTotalPhys / (1024 * 1024));
DWORD virtMB = (DWORD)(ms.dwTotalVirtual / (1024 * 1024));
totalPhysical.Format(_T("physical memory:%lu MB"), physMB);
totalVirtual.Format(_T("virtual memory:%lu MB"), virtMB);
}
int SystemProbe::EnumerateNics()
{
m_nicNames.RemoveAll();
m_nicBandwidths.RemoveAll();
m_nicTraffic.RemoveAll();
const DWORD kObjIdx = 510; // Network Interface
const DWORD kBandwidthCounterIdx = 520; // Current Bandwidth
const DWORD kChunk = 40960;
DWORD bufSize = kChunk;
BYTE* buf = (BYTE*)malloc(bufSize);
if (!buf) return 0;
DWORD type = 0; DWORD ret = 0;
CString idx; idx.Format(_T("%u"), kObjIdx);
for (;;)
{
ret = RegQueryValueEx(HKEY_PERFORMANCE_DATA, idx, 0, &type, buf, &bufSize);
if (ret == ERROR_SUCCESS) break;
if (ret == ERROR_MORE_DATA) {
bufSize += kChunk; BYTE* tmp = (BYTE*)realloc(buf, bufSize);
if (!tmp) { free(buf); return 0; }
buf = tmp; continue;
}
free(buf); return 0;
}
PERF_DATA_BLOCK* pdb = (PERF_DATA_BLOCK*)buf;
PERF_OBJECT_TYPE* pot = (PERF_OBJECT_TYPE*)((BYTE*)pdb + pdb->HeaderLength);
for (LONG oi = 0; oi < (LONG)pdb->NumObjectTypes; ++oi)
{
if (pot->ObjectNameTitleIndex == kObjIdx)
{
DWORD bandwidthOffset = ULONG_MAX;
PERF_COUNTER_DEFINITION* pcd = (PERF_COUNTER_DEFINITION*)((BYTE*)pot + pot->HeaderLength);
for (LONG ci = 0; ci < (LONG)pot->NumCounters; ++ci)
{
if (pcd->CounterNameTitleIndex == kBandwidthCounterIdx)
bandwidthOffset = pcd->CounterOffset;
pcd = (PERF_COUNTER_DEFINITION*)((BYTE*)pcd + pcd->ByteLength);
}
if (bandwidthOffset == ULONG_MAX) break;
PERF_INSTANCE_DEFINITION* pid = (PERF_INSTANCE_DEFINITION*)((BYTE*)pot + pot->DefinitionLength);
for (LONG ii = 0; ii < pot->NumInstances; ++ii)
{
WCHAR* wname = (WCHAR*)((BYTE*)pid + pid->NameOffset);
CHAR nameA[256] = {0};
WideCharToMultiByte(CP_ACP, 0, wname, -1, nameA, sizeof(nameA), nullptr, nullptr);
PERF_COUNTER_BLOCK* pcb = (PERF_COUNTER_BLOCK*)((BYTE*)pid + pid->ByteLength);
DWORD bandwidth = *(DWORD*)((BYTE*)pcb + bandwidthOffset);
m_nicNames.AddTail(CString(nameA));
m_nicBandwidths.AddTail(bandwidth);
m_nicTraffic.AddTail(0);
pid = (PERF_INSTANCE_DEFINITION*)((BYTE*)pid + pid->ByteLength + pcb->ByteLength);
}
}
pot = (PERF_OBJECT_TYPE*)((BYTE*)pot + pot->TotalByteLength);
}
free(buf);
return (int)m_nicNames.GetCount();
}
void SystemProbe::GetNicName(CString& nicName, int index)
{
nicName.Empty();
POSITION p = m_nicNames.FindIndex(index);
if (!p) return;
CString name = m_nicNames.GetAt(p);
p = m_nicBandwidths.FindIndex(index);
if (!p) { nicName = name; return; }
DWORD bw = m_nicBandwidths.GetAt(p);
CString s; s.Format(_T("%lu"), bw);
nicName = name + s;
}
void SystemProbe::EnumerateDisks(DWORD& outCount, CString outDescriptions[])
{
DWORD mask = GetLogicalDrives();
UINT count = 0;
for (DWORD v = mask; v; v >>= 1) if (v & 1) ++count;
if (outCount < count) { return; }
outCount = count;
UINT len = GetLogicalDriveStrings(0, nullptr);
CArray<TCHAR, TCHAR> buf; buf.SetSize(len + 2);
GetLogicalDriveStrings(len, buf.GetData());
UINT idx = 0;
for (LPTSTR p = buf.GetData(); *p; p += lstrlen(p) + 1)
{
CString root = p;
UINT type = GetDriveType(root);
CString kind;
switch (type)
{
case DRIVE_FIXED: kind = _T("Local disk"); break;
case DRIVE_CDROM: kind = _T("Optical"); break;
case DRIVE_REMOVABLE:kind = _T("Removable"); break;
case DRIVE_REMOTE: kind = _T("Network"); break;
case DRIVE_RAMDISK: kind = _T("RAM disk"); break;
default: kind = _T("Unknown"); break;
}
ULARGE_INTEGER freeCaller = {0}, total = {0}, freeAll = {0};
BOOL ok = GetDiskFreeSpaceEx(root, &freeCaller, &total, &freeAll);
CString sTotal, sFree;
if (ok) {
sTotal.Format(_T("Total %0.0fMB"), (double)total.QuadPart / 1024.0 / 1024.0);
sFree.Format(_T(" Free %0.0fMB"), (double)freeCaller.QuadPart / 1024.0 / 1024.0);
}
outDescriptions[idx++] = kind + _T(" (") + root + _T("):") + sTotal + sFree;
}
}
void SystemProbe::EnumerateVideoAdapters(DWORD& outCount, CString outNames[])
{
outCount = 0;
HKEY hServices = nullptr, hEnum = nullptr;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services"), 0, KEY_READ, &hServices) != ERROR_SUCCESS)
return;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Enum"), 0, KEY_READ, &hEnum) != ERROR_SUCCESS)
{
RegCloseKey(hServices); return;
}
for (DWORD i = 0;; ++i)
{
TCHAR sub[256] = {0}; DWORD cch = _countof(sub);
if (RegEnumKeyEx(hServices, i, sub, &cch, nullptr, nullptr, nullptr, nullptr) == ERROR_NO_MORE_ITEMS) break;
HKEY hSrv = nullptr;
if (RegOpenKeyEx(hServices, sub, 0, KEY_READ, &hSrv) != ERROR_SUCCESS) continue;
TCHAR group[64] = {0}; DWORD cb = sizeof(group); DWORD type = 0;
LONG lr = RegQueryValueEx(hSrv, TEXT("Group"), nullptr, &type, (LPBYTE)group, &cb);
if (lr != ERROR_SUCCESS || _tcscmp(group, TEXT("Video")) != 0) { RegCloseKey(hSrv); continue; }
HKEY hSrvEnum = nullptr;
if (RegOpenKeyEx(hSrv, TEXT("Enum"), 0, KEY_READ, &hSrvEnum) != ERROR_SUCCESS) { RegCloseKey(hSrv); continue; }
DWORD count = 0; cb = sizeof(count);
if (RegQueryValueEx(hSrvEnum, TEXT("Count"), nullptr, &type, (LPBYTE)&count, &cb) != ERROR_SUCCESS)
{ RegCloseKey(hSrvEnum); RegCloseKey(hSrv); continue; }
outCount = count;
for (DWORD j = 0; j < count; ++j)
{
TCHAR idxStr[32]; wsprintf(idxStr, TEXT("%u"), j);
TCHAR path[512] = {0}; cb = sizeof(path);
if (RegQueryValueEx(hSrvEnum, idxStr, nullptr, &type, (LPBYTE)path, &cb) != ERROR_SUCCESS) continue;
HKEY hDev = nullptr;
if (RegOpenKeyEx(hEnum, path, 0, KEY_READ, &hDev) != ERROR_SUCCESS) continue;
TCHAR name[512] = {0}; DWORD cbb = sizeof(name);
if (RegQueryValueEx(hDev, TEXT("FriendlyName"), nullptr, &type, (LPBYTE)name, &cbb) != ERROR_SUCCESS)
{
cbb = sizeof(name);
if (RegQueryValueEx(hDev, TEXT("DeviceDesc"), nullptr, &type, (LPBYTE)name, &cbb) != ERROR_SUCCESS) name[0] = 0;
}
outNames[j] = name;
RegCloseKey(hDev);
}
RegCloseKey(hSrvEnum);
RegCloseKey(hSrv);
break; // first matching video service is enough
}
RegCloseKey(hEnum);
RegCloseKey(hServices);
}
Notes
- The registry-based CPU/GPU detection is fast and broadly compatible across Windows NT family versions.
- NIC enumeration via HKEY_PERFORMANCE_DATA reads the "Network Interface" performance object (index 510) and its "Current Bandwidth" counter (index 520).
- For memory, GlobalMemoryStatusEx is preferable on modern systems, but GlobalMemoryStatus is maintained above for compatibility with the original data types.
WMI-based approach
WMI (Windows Menagement Instrumentation) exposes a uniform model for OS and hardware. Its easy to query but typically slower than direct Win32/registry calls.
WmiClient.h
#pragma once
#include <atlbase.h>
#include <afxpriv.h>
#include <WbemIdl.h>
#pragma comment(lib, "WbemUuid.lib")
class WmiClient
{
public:
WmiClient() : m_svc(nullptr), m_loc(nullptr), m_enum(nullptr), m_obj(nullptr) {}
~WmiClient() {}
HRESULT Initialize();
HRESULT Shutdown();
BOOL QuerySingle(CString wmiClass, CString member, CString& outValue);
BOOL QueryFields(CString wmiClass, CString members[], int count, CString& outJoined);
private:
void VariantToCString(const VARIANT* v, CString& out) const;
private:
IWbemServices* m_svc;
IWbemLocator* m_loc;
IEnumWbemClassObject* m_enum;
IWbemClassObject* m_obj;
};
WmiClient.cpp
#include "StdAfx.h"
#include "WmiClient.h"
HRESULT WmiClient::Initialize()
{
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) return hr;
hr = CoInitializeSecurity(
nullptr,
-1,
nullptr,
nullptr,
RPC_C_AUTHN_LEVEL_PKT,
RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr,
EOAC_NONE,
nullptr);
// proceed even if RPC_E_TOO_LATE etc.
hr = CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (void**)&m_loc);
if (FAILED(hr)) return hr;
hr = m_loc->ConnectServer(CComBSTR(L"ROOT\\CIMV2"), nullptr, nullptr, 0, 0, 0, 0, &m_svc);
if (FAILED(hr)) return hr;
hr = CoSetProxyBlanket(
m_svc,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
nullptr,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr,
EOAC_NONE);
return hr;
}
HRESULT WmiClient::Shutdown()
{
if (m_enum) { m_enum->Release(); m_enum = nullptr; }
if (m_obj) { m_obj->Release(); m_obj = nullptr; }
if (m_svc) { m_svc->Release(); m_svc = nullptr; }
if (m_loc) { m_loc->Release(); m_loc = nullptr; }
CoUninitialize();
return S_OK;
}
BOOL WmiClient::QuerySingle(CString wmiClass, CString member, CString& outValue)
{
outValue.Empty();
if (!m_svc) return FALSE;
CComBSTR q(L"SELECT * FROM "); q += CComBSTR(wmiClass);
HRESULT hr = m_svc->ExecQuery(CComBSTR(L"WQL"), q,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
nullptr, &m_enum);
if (FAILED(hr)) return FALSE;
VARIANT v; VariantInit(&v);
ULONG fetched = 0;
BOOL ok = FALSE;
hr = m_enum->Next(WBEM_INFINITE, 1, &m_obj, &fetched);
if (SUCCEEDED(hr) && fetched == 1)
{
hr = m_obj->Get(CComBSTR(member), 0, &v, nullptr, nullptr);
if (SUCCEEDED(hr)) { VariantToCString(&v, outValue); ok = TRUE; }
VariantClear(&v);
}
if (m_obj) { m_obj->Release(); m_obj = nullptr; }
if (m_enum) { m_enum->Release(); m_enum = nullptr; }
return ok;
}
BOOL WmiClient::QueryFields(CString wmiClass, CString members[], int count, CString& outJoined)
{
outJoined.Empty();
if (!m_svc) return FALSE;
CComBSTR q(L"SELECT * FROM "); q += CComBSTR(wmiClass);
HRESULT hr = m_svc->ExecQuery(CComBSTR(L"WQL"), q,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
nullptr, &m_enum);
if (FAILED(hr)) return FALSE;
VARIANT v; VariantInit(&v);
ULONG fetched = 0; BOOL ok = FALSE;
hr = m_enum->Next(WBEM_INFINITE, 1, &m_obj, &fetched);
if (SUCCEEDED(hr) && fetched == 1)
{
for (int i = 0; i < count; ++i)
{
CString piece;
if (SUCCEEDED(m_obj->Get(CComBSTR(members[i]), 0, &v, nullptr, nullptr)))
{
VariantToCString(&v, piece);
if (!outJoined.IsEmpty()) outJoined += _T("\t");
outJoined += piece;
VariantClear(&v);
ok = TRUE;
}
}
outJoined += _T("\r\n");
}
if (m_obj) { m_obj->Release(); m_obj = nullptr; }
if (m_enum) { m_enum->Release(); m_enum = nullptr; }
return ok;
}
void WmiClient::VariantToCString(const VARIANT* pv, CString& out) const
{
USES_CONVERSION;
out.Empty();
switch (pv->vt)
{
case VT_BSTR:
out = W2T(pv->bstrVal); break;
case VT_BOOL:
out = (pv->boolVal == VARIANT_TRUE) ? _T("yes") : _T("no"); break;
case VT_I4:
out.Format(_T("%ld"), pv->lVal); break;
case VT_UI4:
out.Format(_T("%lu"), pv->ulVal); break;
case VT_UI1:
out.Format(_T("%u"), pv->bVal); break;
case (VT_BSTR | VT_ARRAY):
{
CComBSTR HUGEP* pData = nullptr;
if (SUCCEEDED(SafeArrayAccessData(pv->parray, (void HUGEP**)&pData)))
{
out = W2T(pData->m_str);
SafeArrayUnaccessData(pv->parray);
}
break;
}
case (VT_I4 | VT_ARRAY):
{
BYTE HUGEP* pData = nullptr;
LONG lo = 0, hi = 0;
SafeArrayGetLBound(pv->parray, 1, &lo);
SafeArrayGetUBound(pv->parray, 1, &hi);
if (SUCCEEDED(SafeArrayAccessData(pv->parray, (void HUGEP**)&pData)))
{
CString t;
hi = min<LONG>(hi, MAX_PATH * 2 - 1);
for (LONG i = lo; i <= hi; ++i) { t.Format(_T("%02X"), pData[i]); out += t; }
SafeArrayUnaccessData(pv->parray);
}
break;
}
default:
break;
}
}
Usage sequence
- Construct WmiClient.
- Call Initialize().
- Invoke QuerySingle or QueryFields to retrieve properties from a WMI class.
- Call Shutdown() when finished.
Examples
- CPU name via WMI
- Class: Win32_Processor
- Property: Caption
- Example: QuerySingle(_T("Win32_Processor"), _T("Caption"), value)
- Multiple CPU properties
- Properties array: {"Caption", "CurrentClockSpeed", "DeviceID", "Manufacturer"}
- Example: QueryFields(_T("Win32_Processor"), arr, 4, value)
Frequently used WMI classes
Hardware
- Win32_Processor
- Win32_PhysicalMemory
- Win32_Keyboard
- Win32_PointingDevice
- Win32_FloppyDrive
- Win32_DiskDrive
- Win32_CDROMDrive
- Win32_BaseBoard
- Win32_BIOS
- Win32_ParallelPort
- Win32_SerialPort
- Win32_SerialPortConfiguration
- Win32_SoundDevice
- Win32_SystemSlot
- Win32_USBController
- Win32_NetworkAdapter
- Win32_NetworkAdapterConfiguration
- Win32_Printer
- Win32_PrinterConfiguration
- Win32_PrintJob
- Win32_TCPIPPrinterPort
- Win32_POTSModem
- Win32_POTSModemToSerialPort
- Win32_DesktopMonitor
- Win32_DisplayConfiguration
- Win32_DisplayControllerConfiguration
- Win32_VideoController
- Win32_VideoSettings
Operating system
- Win32_TimeZone
- Win32_SystemDriver
- Win32_DiskPartition
- Win32_LogicalDisk
- Win32_LogicalDiskToPartition
- Win32_LogicalMemoryConfiguration
- Win32_PageFile
- Win32_PageFileSetting
- Win32_BootConfiguration
- Win32_ComputerSystem
- Win32_OperatingSystem
- Win32_StartupCommand
- Win32_Service
- Win32_Group
- Win32_GroupUser
- Win32_UserAccount
- Win32_Process
- Win32_Thread
- Win32_Share
- Win32_NetworkClient
- Win32_NetworkProtocol