☰
C语言跨平台获取CPU序列号、硬盘序列号与网卡MAC实战
2026/10/16 6:41:58 网站建设 项目流程

简介:面向系统监控、设备识别与故障排查场景,这份C语言硬件信息获取工具包提供了一套跨平台的实现方案,覆盖Windows与Linux下的CPU序列号、硬盘序列号、网卡IP/MAC地址及网络连接状态读取,支持判断网卡是否插入网线。压缩包共2个文件,分别是1个头文件与1个C++源文件,整体仅7KB,结构精简、模块清晰:头文件负责接口声明,源文件完成具体逻辑,并用条件编译处理WMI、/proc/cpuinfo、/sys/block等系统差异。当前已有3100人学习下载,适合系统编程初学者动手验证,也便于开发者在实际项目中直接集成。通过阅读和复用这些代码,读者可以掌握CreateFileW、DeviceIoControl、GetAdaptersAddresses、ioctl等关键系统接口的调用方式,学会从虚拟文件系统中解析硬件标识,并形成一套可迁移到其他平台的硬件信息采集思路,是提升底层编程与系统管理能力的实用素材。

1. 用C语言获取CPU序列号、硬盘序列号与网卡信息:Linux和Windows各走各的路

软件授权按机器绑定、资产盘点要出硬件清单、远程运维判断设备是否掉线,这些场景绕不开同一件事:在Linux和Windows上用C语言稳定获取CPU序列号、硬盘序列号、网卡IP、MAC地址,顺便判断网线是否还插着。代码看着不多,难点全在平台差异和权限边界上。Windows里的WMI查询、DeviceIoControl,Linux里的sysfs、ioctl,两条路线各有脾气,稍不注意就会读出脏数据或直接翻车。这篇笔记以一份跨平台采集模块为主体,把两套系统的实现拆开讲,代码可以直接编译验证,最后给出五个真实踩坑记录,帮你把硬件信息采集这块硬骨头一次啃下来。

2. 跨平台选型:WMI、注册表、sysfs与ioctl,四条路线的取舍逻辑

2.1 Windows:WMI虽慢,但跨Win7到Win11最稳的一条路径

Windows下能读硬件信息的入口很多:注册表、WMI、DeviceIoControl、GetAdaptersAddresses。注册表读CPU序列号的路径是HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\ProcessorId,读取极快,但坑在固件默认值上。很多主板出厂时BIOS里填的是“To be filled by O.E.M.”,这个值一旦被注册表透传出来,你的授权系统就会把每台机器当成同一台机器。我见过有人上线半年后才被这个问题反噬,排查过程极度折磨。

WMI的方式是Win32_Processor.ProcessorId、Win32_DiskDrive.SerialNumber、Win32_NetworkAdapter,通过COM接口调用,代码确实比注册表长,但胜在稳定。WMI由系统组件统一管理,从Win7到Win11没有遇到查询结果结构变化的情况。缺点是首次查询要初始化COM,单次查询有几十毫秒开销,在批量盘点时这个开销会被放大。

方案兼容性权限要求查询开销主要风险
注册表 ProcessorId基本通用普通用户极低OEM默认值污染
WMI 查询跨版本稳定普通用户几十毫秒级COM初始化易错
DeviceIoControl稳定部分环境需管理员低物理/逻辑盘选择
GetAdaptersAddressesVista以后都有普通用户低结构体版本分支多

这套选择逻辑和资源里代码的走向一致:CPU序列号、硬盘序列号走WMI,网卡信息走GetAdaptersAddresses,链路状态另用NDIS OID兜底。后面每个模块都会按这个主路线展开。

2.2 Linux:sysfs优先,ioctl兜底,root不是银弹

Linux的硬件信息入口比Windows更直白,因为内核把大部分设备属性都挂在了sysfs上。读取网卡MAC地址只需读/sys/class/net/eth0/address,网线状态看/sys/class/net/eth0/carrier,硬盘序列号在/sys/block/sda/device/serial。这些文件都是普通文本,普通用户可读,不需要root,代码逻辑就是fopen加fgets,非常清爽。但sysfs有个前提:内核已经识别了这个设备,且驱动把对应的属性塞进了sysfs。部分嵌入式板卡的驱动偷工减料,serial文件可能不存在。

ioctl是更底层的路径,比如用SIOCETHTOOL查网线状态、用HDIO_GET_IDENTITY查磁盘序列号,都需要root和真实的设备节点。问题是,root权限并不总是等同于能拿到数据:在容器里mount了/dev但没给CAP_SYS_ADMIN,打开设备节点照样报权限错误;在NVMe盘上跑基于老IDE指令的HDIO_GET_IDENTITY,直接返回EINVAL。我现在的习惯策略是:sysfs作为第一路径,ioctl作为兜底,两者都失败时返回明确的错误码,而不是让调用方猜。

2.3 统一接口层:把平台差异关在三个函数后面

为了让这段代码在双平台上可维护,我按资源里常见的方式定义了一个简明的头文件,把平台逻辑封装成三个对外函数:

/* hw_info.h */ #ifndef HW_INFO_H #define HW_INFO_H #include <stddef.h> #ifdef __cplusplus extern "C" { #endif /* 成功返回0,失败返回负值:-1设备不存在,-2权限不足,-3数据格式异常 */ int hw_get_cpu_serial(char *buf, size_t buf_len); int hw_get_disk_serial(const char *dev_name, char *buf, size_t buf_len); /* 回调方式枚举网卡,返回0继续,返回非0则停止 */ typedef int (*hw_net_callback)(const char *name, const unsigned char *mac, int mac_len, const char *ip, int link_up); int hw_net_enum(hw_net_callback cb); #ifdef __cplusplus } #endif #endif

这个头文件定了三件事:第一个函数读CPU序列号;第二个函数接收设备名参数,比如Windows下的\\.\PhysicalDrive0或Linux下的sda;第三个函数用回调方式枚举网卡,目的在避免在回调里动态分配内存,也方便调用方直接做结构化存储。错误码统一约定为负值,避免把0和失败混为一谈。代码写完后,上层调用根本不用关心当前是什么操作系统,平台判断全部塞在.c文件的#ifdef _WIN32里。

3. 核心代码落地:CPU序列号、硬盘序列号与网卡信息的实现细节

3.1 CPU序列号:WMI是主路,CPUID指令是备胎

Windows下我优先用WMI查询Win32_Processor.ProcessorId,因为WMI返回的是SMBIOS固化数据,格式一致。核心查询代码大致如下:

#include <windows.h> #include <wbemidl.h> #include <stdio.h> #pragma comment(lib, "wbemuuid.lib") int get_cpu_serial_windows(char *out, size_t out_len) { IWbemLocator *locator = NULL; IWbemServices *service = NULL; IEnumWbemClassObject *enumerator = NULL; IWbemClassObject *obj = NULL; VARIANT value; int ret = -1; HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) return -1; hr = CoCreateInstance(&CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, (LPVOID *)&locator); if (FAILED(hr)) goto cleanup; hr = locator->lpVtbl->ConnectServer(locator, L"root\\cimv2", NULL, NULL, 0, 0, NULL, &service); if (FAILED(hr)) goto cleanup; hr = service->lpVtbl->ExecQuery(service, L"WQL", L"SELECT ProcessorId FROM Win32_Processor", 0, NULL, &enumerator); if (FAILED(hr)) goto cleanup; if (enumerator->lpVtbl->Next(enumerator, 60000, 1, &obj, NULL) == S_OK && obj != NULL) { VariantInit(&value); if (obj->lpVtbl->Get(obj, L"ProcessorId", 0, &value, NULL, NULL) == S_OK && value.vt == VT_BSTR) { WideCharToMultiByte(CP_UTF8, 0, value.bstrVal, -1, out, (int)out_len, NULL, NULL); ret = 0; } VariantClear(&value); obj->lpVtbl->Release(obj); } cleanup: if (enumerator) enumerator->lpVtbl->Release(enumerator); if (service) service->lpVtbl->Release(service); if (locator) locator->lpVtbl->Release(locator); CoUninitialize(); return ret; }

这段代码是标准WMI流程:初始化COM、创建Locator、连接命名空间、执行WQL、取第一条记录、读属性。注意初始化时用了COINIT_MULTITHREADED,如果你按默认的CoInitialize走单线程模型,在同一个线程里做过套接字操作后会概率性地返回RPC_E_CHANGED_MODE,属于那种复现率不稳定、让人想砸键盘的玄学故障。这里把参数写死成MTA模型,能绕开大部分问题。

Linux上没有对应的WMI,只能组合CPUID指令的寄存器输出,当作一个近似的“序列号”来用:

#include <cpuid.h> #include <stdio.h> #include <stdint.h> int get_cpu_serial_linux(char *out, size_t out_len) { unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0; if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { return -1; } snprintf(out, out_len, "%08x-%08x-%08x-%08x", eax, ebx, ecx, edx); return 0; }

__get_cpuid是GCC系编译器在cpuid.h里提供的封装,比直接内联汇编安全。它返回的是CPUID叶1的四个寄存器值,其中包含步进、型号、特性标志位等信息,组合后足以区分同型号下的不同批次的处理器。但它不是真正意义上的出厂序列号,在虚拟机里会被Hypervisor改写,所以这段代码在资源中更适合做辅助指纹,不作为唯一绑定依据。

3.2 硬盘序列号:DeviceIoControl与sysfs的对称实现

Windows下读硬盘序列号,最通用的是DeviceIoControl配合IOCTL_STORAGE_QUERY_PROPERTY:

#include <windows.h> #include <winioctl.h> #include <stdio.h> #include <string.h> int get_disk_serial_windows(const char *drive, char *out, size_t out_len) { HANDLE h = CreateFileA(drive, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (h == INVALID_HANDLE_VALUE) { return -1; } STORAGE_PROPERTY_QUERY query; memset(&query, 0, sizeof(query)); query.PropertyId = StorageDeviceProperty; query.QueryType = PropertyStandardQuery; char buffer[4096]; DWORD bytes_returned = 0; BOOL ok = DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), buffer, sizeof(buffer), &bytes_returned, NULL); CloseHandle(h); if (!ok) { return -2; } STORAGE_DEVICE_DESCRIPTOR *desc = (STORAGE_DEVICE_DESCRIPTOR *)buffer; if (desc->SerialNumberOffset == 0) { return -3; } snprintf(out, out_len, "%s", buffer + desc->SerialNumberOffset); return 0; }

CreateFileA的第一个参数是设备路径,Windows上一般传\\.\PhysicalDrive0,代表第一块物理盘。这里用OPEN_EXISTING打开,访问权限设为0是因为查询属性用不到读写。返回值里关键字段是SerialNumberOffset,它是一个相对缓冲区起始的偏移量,也就是说序列号字符串藏在偏移位置之后,直接拿偏移量定位即可。常见错误是把STORAGE_DEVICE_DESCRIPTOR的偏移量混同于固定长度,结果读到一串乱码。

Linux版本的代码更短,直接读sysfs节点:

#include <stdio.h> #include <string.h> int get_disk_serial_linux(const char *dev, char *out, size_t out_len) { char path[256]; snprintf(path, sizeof(path), "/sys/block/%s/device/serial", dev); FILE *fp = fopen(path, "r"); if (!fp) { return -1; } if (fgets(out, (int)out_len, fp) == NULL) { fclose(fp); return -2; } fclose(fp); size_t len = strlen(out); while (len > 0 && (out[len - 1] == '\n' || out[len - 1] == ' ' || out[len - 1] == '\t')) { out[--len] = '\0'; } return 0; }

这里的dev参数传入的是sda、nvme0n1这种块设备名,而不是/dev/sda。因为sysfs的路径规范是以设备名直接拼接。最后那段while循环是为了去掉fgets带进来的换行和尾部空格,这类脏字符在资产比对时会造成严重问题,常有ABC123\n和ABC123匹配不上的事故。

3.3 网卡IP与MAC:枚举接口时绕开字节序陷阱

Windows下推荐用GetAdaptersAddresses替代老旧的GetAdaptersInfo,因为它同时覆盖IPv4和IPv6,且能拿到MAC、状态等字段:

#include <winsock2.h> #include <iphlpapi.h> #include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") void enum_net_windows(void) { ULONG size = 0; GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &size); IP_ADAPTER_ADDRESSES *aa = (IP_ADAPTER_ADDRESSES *)malloc(size); ULONG rc = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, aa, &size); if (rc != NO_ERROR) { free(aa); return; } for (IP_ADAPTER_ADDRESSES *p = aa; p; p = p->Next) { if (p->IfType == IF_TYPE_SOFTWARE_LOOPBACK) { continue; } printf("adapter: %s\n", p->AdapterName); for (int i = 0; i < (int)p->PhysicalAddressLength; i++) { printf("%02X%s", p->PhysicalAddress[i], i < (int)p->PhysicalAddressLength - 1 ? ":" : "\n"); } for (IP_ADAPTER_UNICAST_ADDRESS *u = p->FirstUnicastAddress; u; u = u->Next) { char ip_buf[64] = {0}; if (u->Address.lpSockaddr->sa_family == AF_INET) { SOCKADDR_IN *sin = (SOCKADDR_IN *)u->Address.lpSockaddr; InetNtopA(AF_INET, &sin->sin_addr, ip_buf, sizeof(ip_buf)); } else if (u->Address.lpSockaddr->sa_family == AF_INET6) { SOCKADDR_IN6 *sin6 = (SOCKADDR_IN6 *)u->Address.lpSockaddr; InetNtopA(AF_INET6, &sin6->sin6_addr, ip_buf, sizeof(ip_buf)); } printf(" ip: %s\n", ip_buf); } } free(aa); }

这段代码有几个值得展开的细节。第一次调用GetAdaptersAddresses时传NULL缓冲区,系统会返回ERROR_BUFFER_OVERFLOW,同时把需要的字节数写进size,这是为了拿正确缓冲区长度,常见的错误是直接开一个固定数组然后装不下。第二个细节是MAC地址打印:PhysicalAddress是一个字节数组,不是整数,必须逐字节按%02X输出。如果图省事把四个字节强转成unsigned int再打印,大小端会反转,找半天找不出原因。第三个细节是对回环接口做了过滤,否则Windows会列出Loopback Pseudo-Interface,MAC是全零,容易污染数据。

Linux下枚举网卡的对称实现是socket + SIOCGIFCONF + SIOCGIFHWADDR:

#include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <unistd.h> void enum_net_linux(void) { int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { return; } struct ifconf ifc; char buffer[4096]; ifc.ifc_len = sizeof(buffer); ifc.ifc_buf = buffer; ioctl(fd, SIOCGIFCONF, &ifc); struct ifreq *it = ifc.ifc_req; int count = ifc.ifc_len / (int)sizeof(struct ifreq); for (int i = 0; i < count; i++) { struct ifreq req = it[i]; printf("adapter: %s\n", req.ifr_name); struct ifreq hw = req; if (ioctl(fd, SIOCGIFHWADDR, &hw) == 0) { unsigned char *mac = (unsigned char *)hw.ifr_hwaddr.sa_data; for (int j = 0; j < 6; j++) { printf("%02X%s", mac[j], j < 5 ? ":" : "\n"); } } struct ifreq ip = req; if (ioctl(fd, SIOCGIFADDR, &ip) == 0) { struct sockaddr_in *sin = (struct sockaddr_in *)&ip.ifr_addr; printf(" ip: %s\n", inet_ntoa(sin->sin_addr)); } } close(fd); }

SIOCGIFCONF返回的是所有配置了IP的接口,换句话说,没有IP的接口不会出现在列表里,这在网线检测场景下会有干扰。如果你需要枚举包括 down 状态在内的所有接口,建议换成getifaddrs。另外注意hw.ifr_hwaddr.sa_data的长度是14字节,而MAC只占前6字节,后面是填充,打印时写死6字节即可。

4. 链路状态检测:网线是否插入的两种可靠判断方式

4.1 Windows:先用GetAdaptersAddresses的OperStatus,再用NDIS OID收尾

网线插入状态不完全等于网络连通状态。有人禁用了本地连接时,系统网络列表显示的“已断开”是操作员层面的状态,驱动层面的物理链路可能仍然连着。做资产盘点或远程诊断时,更关心的往往是物理链路。Windows里最快速的状态是GetAdaptersAddresses返回的结构体字段OperStatus,它属于IfOperStatus枚举,等于IfOperStatusUp意味着网卡逻辑上可用,但不等于网线插着。

要拿确切的媒体连接状态,需要走NDIS,通过DeviceIoControl查询OID_GEN_MEDIA_CONNECT_STATUS:

#include <windows.h> #include <ntddndis.h> #include <stdio.h> int query_ndis_link_status(const char *adapter_name) { char path[256]; snprintf(path, sizeof(path), "\\\\.\\%s", adapter_name); HANDLE h = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (h == INVALID_HANDLE_VALUE) { return -1; } DWORD oid = OID_GEN_MEDIA_CONNECT_STATUS; DWORD status = 0; DWORD bytes = 0; BOOL ok = DeviceIoControl(h, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), &status, sizeof(status), &bytes, NULL); CloseHandle(h); if (!ok) { return -2; } return (status == NDIS_MEDIA_STATE_CONNECTED) ? 1 : 0; }

这段代码依赖WDK头文件ntddndis.h,这不是标准Windows SDK自带的头文件。如果你只有MinGW环境,编译会直接卡在头文件缺失上。我的建议是:在普通应用层优先用OperStatus,当你能拿到WDK头文件、且需要判断物理插线时才启用NDIS路径。注意IOCTL_NDIS_QUERY_GLOBAL_STATS的用法在不同Windows版本上表现有差异,所以它只做收尾兜底,不做主判断。

4.2 Linux:ethtool的ETHTOOL_GLINK配合sysfs的carrier,双保险

Linux的链路判断有两条互补路径。一是读sysfs的/sys/class/net/<接口>/carrier,二是通过SIOCETHTOOL调ETHTOOL_GLINK。carrier文件内容为1表示有载波,0表示无载波,读取非常直接:

#include <stdio.h> #include <string.h> int link_status_sysfs(const char *ifname) { char path[256]; snprintf(path, sizeof(path), "/sys/class/net/%s/carrier", ifname); FILE *fp = fopen(path, "r"); if (!fp) { return -1; } int status = -1; if (fscanf(fp, "%d", &status) != 1) { status = -1; } fclose(fp); return status; }

sysfs方式最大的限制是接口必须处于up状态。如果一个接口被ip link set eth0 down,carrier文件会显示为0或直接读取失败,物理插线状态被隐藏了。这就是为什么需要ethtool路径兜底,它对接口状态没那么敏感:

#include <sys/ioctl.h> #include <net/if.h> #include <linux/ethtool.h> #include <linux/sockios.h> #include <string.h> #include <stdio.h> #include <unistd.h> int link_status_ethtool(const char *ifname) { int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { return -1; } struct ifreq ifr; struct ethtool_value eval; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); eval.cmd = ETHTOOL_GLINK; ifr.ifr_data = (void *)&eval; int rc = ioctl(fd, SIOCETHTOOL, &ifr); close(fd); if (rc != 0) { return -2; } return eval.data; }

ETHTOOL_GLINK会调用网卡驱动的get_link回调,大多数有线网卡驱动都支持。返回值里eval.data为1表示物理链路已建立。如果你的网卡驱动没有实现该回调,ioctl会返回EOPNOTSUPP,这时再退回读 carrier 文件。平台侧双保险就是:先ETHTOOL_GLINK,失败再读sysfs,两者同时失败才报错。

5. 避坑与排查:五个实际翻车现场

5.1 WMI查询偶发返回空字符串,伴随崩溃

现象:程序在部分机器上跑,WMI查询结果正常,但在另一些机器上会偶发返回空字符串,运气不好时在线程退出阶段直接崩溃。排查发现崩溃点是VariantClear访问了未初始化的VARIANT。

原因:COM初始化线程模型不一致。默认CoInitialize走STA模型,而采集线程在网络初始化时又调用了套接字函数,线程模型被切换状态搞乱,导致IWbemClassObject::Get返回数据异常。

解决:进程启动入口统一改成CoInitializeEx(NULL, COINIT_MULTITHREADED),所有WMI操作保持在同一个MTA线程同步执行,并且每次查询前先判断FAILED(hr),不为S_OK时直接跳到清理段,不让未初始化的VARIANT进入清理流程。

5.2 CPU序列号读出“To be filled by O.E.M.”

现象:从注册表读取ProcessorId,几十台机器返回完全相同的默认字符串,授权模块把每台机器识别成了同一台。

原因:主板厂商在BIOS里没有写入实际的处理器序列号,注册表只能透传默认填充,旧主板尤为常见。

解决:不再把注册表当主来源,换WMI查询Win32_Processor.ProcessorId;如果WMI返回的值同样是OEM默认字符串,把它标记为“不可信指纹”,降级组合CPUID寄存器值和MAC地址生成复合ID。从那以后我判断这类数据是否可信,都是先白名单过滤OEM默认值,再进比对逻辑。

5.3 Linux下明明用root,HDIO_GET_IDENTITY却报EINVAL

现象:root权限运行程序,打开/dev/sda成功,但ioctl(fd, HDIO_GET_IDENTITY, ...)返回EINVAL。

原因:磁盘是NVMe接口,内核走的是NVMe协议栈,老式ATA命令集在它上面根本不适用;或者宿主机加了一层SCSI翻译层,部分命令被过滤。

解决:判断磁盘类型后分流。SATA/SCSI走HDIO_GET_IDENTITY,NVMe设备直接读sysfs的/sys/block/nvme0n1/device/serial,不要混用。更保险的做法是干脆统一走sysfs,ioctl只用来兜底。

5.4 MAC地址高低字节颠倒,打印出来错乱

现象:Windows下用GetAdaptersAddresses取MAC,64位系统上打印结果变成55:AA:BB:...这种非常怪异的排列,拿去和路由器列表对不上。

原因:PhysicalAddress是字节数组,数据按网络字节序填充在内存里,直接按整数类型强转后再取整型打印,高低位顺序就反转了。

解决:强制逐字节处理,每字节按%02X输出,并在打印时用冒号分隔。MAC格式统一为小写字母加冒号,能和Linux/sys/class/net/<接口>/address的格式对齐,后续做跨平台比对就省心很多。

5.5 虚拟机里所有序列号都相同,授权系统直接失效

现象:在某云主机和本地KVM虚拟机上跑采集程序,CPU序列号、硬盘序列号完全相同,授权绑定策略形同虚设。

原因:虚拟化平台通过SMBIOS层批量为所有虚拟机分配相同的硬件标识,CPUID指令也被Hypervisor统一改写。

解决:遇到全零或全相同的SP值类型数据,不再单纯依赖CPU序列号做绑定,把“主板UUID + 网卡MAC + 磁盘型号”组合成复合指纹,并允许人为添加例外。这个逻辑需要在采集层就预留一个“指纹可信度”字段,后续在服务端做策略判断时才能区分物理机和虚拟机。

6. 进阶:把采集结果落成JSON并做好静默缓存

前面代码解决的是“读得出”,实际应用里还要回答“怎么存、怎么防重复查询”。我通常把采集结果封装成一个简单的JSON结构,结构如下:

typedef struct hw_item_s { const char *name; const char *value; } hw_item_t; const hw_item_t items[] = { {"cpu_serial", cpu_serial_buf}, {"disk_serial", disk_serial_buf}, {"mac_address", mac_buf}, {"link_status", link_status_buf}, {"ip_address", ip_buf} };

输出JSON时直接遍历这个数组,转义双引号和反斜杠后拼进缓冲;推荐在每次采集计算一个轻量的哈希值,和上次结果比对,没有变化就直接复用上次缓存。因为WMI查询和COM初始化是整套采集里最耗时的部分,每次开机自检都跑全套,浪费几十毫秒不说,还可能因为连续调用触达WMI服务的速率限制。

对这段代码执行gcc -o hwinfo hw_info.c编译时,记得给Linux版加-D_GNU_SOURCE,Windows版用MSVC或MinGW都会自动带上_WIN32宏,不需要额外预处理。排查问题时先在每种系统上跑一遍采集,比对输出里的错误码:-1是设备不存在,-2是权限不足,-3是数据异常,这个约定比直接看一串0xFFFFFFFF直观得多。

从那以后,我每次在采集模块里改动一个平台分支,都会强制走一遍“32/64位、普通用户/管理员、物理机/虚拟机、SATA/NVMe、网线插拔”这五个场景的验证清单,确认错误码和输出都没有漂移再合入版本。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询