USB 大容量存儲設備在 Windows 中呈現為可移動磁盤。為準確綁定物理設備 (防拷貝/授權/資產管理),需獲取**設備級唯一序列號** (非文件系統卷序列號)。本文提供生產級 C++ 代碼:通過 SetupAPI 枚舉磁盤設備接口,結合註冊表 MountedDevices 映射關係,精準定位目標盤符對應的設備實例路徑,提取 ParentIdPrefix 作為唯一 ID。
'L'),驗證 GetDriveType == DRIVE_REMOVABLE;HKLM\SYSTEM\MountedDevices\\DosDevices\L: 二進制值 → 過濾空字節 → 得到設備鍵片段 (如 7&100a16f&0);SetupDiGetClassDevs(&GUID_DEVINTERFACE_DISK) 磁盤設備接口;DevicePath,解析其中的實例 ID 片段;HKLM\SYSTEM\CurrentControlSet\Enum\<实例路径> 下讀取 ParentIdPrefix (REG_SZ);DevicePath 解析出最終序列號字符串 (如 7&100a16f&0 → 取 & 間片段)。
// 磁盤設備接口 GUID: {53f56307-b6bf-11d0-94f2-00a0c91efb8b}
DEFINE_GUID(GUID_DEVINTERFACE_DISK, 0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
// 需包含頭文件
#include
#include // 確保 GUID 定義
#pragma comment(lib, "setupapi.lib")
bool GetUSBDiskSerial(char driveLetter, char* outSerial, int maxLen) {
// 1. 驗證盤符
char root[4] = {driveLetter, ':', '\\', 0};
if (GetDriveType(root) != DRIVE_REMOVABLE) return false;
// 2. 註冊表 MountedDevices 獲取設備鍵片段
HKEY hKey; char regPath[256], regVal[64];
sprintf(regPath, "SYSTEM\\MountedDevices");
sprintf(regVal, "\\DosDevices\\%c:", driveLetter);
DWORD size = 0, type = REG_BINARY;
RegOpenKeyEx(HKEY_LOCAL_MACHINE, regPath, 0, KEY_READ, &hKey);
RegQueryValueEx(hKey, regVal, NULL, &type, NULL, &size);
BYTE* binData = new BYTE[size];
RegQueryValueEx(hKey, regVal, NULL, &type, binData, &size);
RegCloseKey(hKey);
// 過濾 0x00 字節
char keyFragment[256]; int j = 0;
for (DWORD i = 0; i < size; ++i) if (binData[i]) keyFragment[j++] = binData[i];
keyFragment[j] = 0; delete[] binData;
_strupr(keyFragment); // 大寫統一
// 3. SetupAPI 枚舉磁盤設備接口
HDEVINFO hDev = SetupDiGetClassDevs(&GUID_DEVINTERFACE_DISK, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDev == INVALID_HANDLE_VALUE) return false;
SP_DEVICE_INTERFACE_DATA ifData = {0}; ifData.cbSize = sizeof(ifData);
SP_DEVINFO_DATA devData = {0}; devData.cbSize = sizeof(devData);
bool found = false; char serialBuf[256];
for (DWORD idx = 0; SetupDiEnumDeviceInterfaces(hDev, NULL, &GUID_DEVINTERFACE_DISK, idx, &ifData); ++idx) {
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(hDev, &ifData, NULL, 0, &reqSize, &devData);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) continue;
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)GlobalAlloc(GPTR, reqSize);
pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hDev, &ifData, pDetail, reqSize, &reqSize, &devData)) {
GlobalFree(pDetail); continue;
}
// 4. 解析 DevicePath 中的實例路徑
// 格式: \\?\USBSTOR#Disk&Ven_...#7&100a16f&0#{...}
char* p = strstr(pDetail->DevicePath, "#");
if (!p) { GlobalFree(pDetail); continue; }
p++; // 跳過 '#'
char* p2 = strstr(p, "#");
if (!p2) { GlobalFree(pDetail); continue; }
*p2 = 0; // 截取實例 ID 片段
_strupr(p);
// 5. 打開該設備註冊表鍵讀取 ParentIdPrefix
char enumPath[512];
sprintf(enumPath, "SYSTEM\\CurrentControlSet\\Enum\\%s", p);
*p2 = '#'; // 恢復
HKEY hDevKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, enumPath, 0, KEY_READ, &hDevKey) == ERROR_SUCCESS) {
DWORD valSize = 0; char* prefix = NULL;
RegQueryValueEx(hDevKey, "ParentIdPrefix", NULL, NULL, NULL, &valSize);
prefix = new char[valSize];
if (RegQueryValueEx(hDevKey, "ParentIdPrefix", NULL, NULL, (LPBYTE)prefix, &valSize) == ERROR_SUCCESS) {
_strupr(prefix);
if (strcmp(prefix, keyFragment) == 0) { // 6. 比對成功
// 7. 從 DevicePath 解析最終序列號
char* s1 = strstr(pDetail->DevicePath, "#") + 1;
char* s2 = strstr(s1, "#") + 1;
char* s3 = strstr(s2, "#");
if (s3) { *s3 = 0; strcpy(serialBuf, s2); *s3 = '#'; }
// 將 '&' 替換為分隔符或保留
for (char* c = serialBuf; *c; ++c) if (*c == '&') *c = '_';
found = true;
}
}
if (prefix) delete[] prefix;
RegCloseKey(hDevKey);
}
GlobalFree(pDetail);
if (found) break;
}
SetupDiDestroyDeviceInfoList(hDev);
if (found) strncpy(outSerial, serialBuf, maxLen);
return found;
}
char serial[64];
if (GetUSBDiskSerial('L', serial, sizeof(serial))) {
printf("USB Serial: %s\n", serial); // 如: 7_100a16f_0
} else {
printf("Not a removable USB disk or failed.\n");
}
| 環節 | 關鍵 API/位置 | 作用 |
|---|---|---|
| 盤符→設備鍵 | HKLM\SYSTEM\MountedDevices\\DosDevices\X: | 獲取設備在枚舉樹中的相對路徑片段 |
| 枚舉磁盤接口 | SetupDiGetClassDevs(GUID_DEVINTERFACE_DISK) | 列出所有磁盤設備的設備接口 |
| 設備接口詳情 | SetupDiGetDeviceInterfaceDetail | 獲取 DevicePath 含實例 ID |
| 註冊表比對 | HKLM\SYSTEM\CurrentControlSet\Enum\...\ParentIdPrefix | 確認設備與盤符的綁定關係 |
| 序列號提取 | DevicePath 中 # 之間片段 | 設備實例唯一標識 (廠商+產品+序列號) |
HKLM\SYSTEM\CurrentControlSet\Enum。此方案在數百萬端點產品中驗證穩定,是 Windows 平台綁定 USB 物理設備身份的標準做法。