標準 IOCTL_DISK_GET_DRIVE_GEOMETRY 僅能取得基本幾何參數。若需硬碟出廠序號、韌體版本、型號、支援的傳輸模式 (DMA/PIO/UltraDMA) 等詳細資訊,必須透過 ATA/ATAPI IDENTIFY DEVICE 命令,配合 DeviceIoControl 送至埠驅動程式。本文提供完整 C++ 結構定義、核心邏輯與兩種存取路徑 (IDE 直通 / SCSI Miniport)。
// IOCTL 控制碼
#define DFP_SEND_DRIVE_COMMAND CTL_CODE(IOCTL_DISK_BASE, 0x0021, METHOD_BUFFERED, FILE_READ_ACCESS|FILE_WRITE_ACCESS)
#define DFP_RECEIVE_DRIVE_DATA CTL_CODE(IOCTL_DISK_BASE, 0x0022, METHOD_BUFFERED, FILE_READ_ACCESS|FILE_WRITE_ACCESS)
#define IOCTL_SCSI_MINIPORT 0x0004D008
#define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI<<16)+0x0501)
// ATA 命令
#define IDE_ATA_IDENTIFY 0xEC
// IDE 任務檔暫存器
typedef struct _IDEREGS {
BYTE bFeaturesReg, bSectorCountReg, bSectorNumberReg;
BYTE bCylLowReg, bCylHighReg, bDriveHeadReg, bCommandReg, bReserved;
} IDEREGS;
// 驅動程式回傳狀態
typedef struct _DRIVERSTATUS {
BYTE bDriverError, bIDEStatus, bReserved[2];
DWORD dwReserved[2];
} DRIVERSTATUS;
// 輸入緩衝區
typedef struct _SENDCMDINPARAMS {
DWORD cBufferSize; IDEREGS irDriveRegs; BYTE bDriveNumber; BYTE bReserved[3];
DWORD dwReserved[4]; BYTE bBuffer[1];
} SENDCMDINPARAMS;
// 輸出緩衝區
typedef struct _SENDCMDOUTPARAMS {
DWORD cBufferSize; DRIVERSTATUS DriverStatus; BYTE bBuffer[1];
} SENDCMDOUTPARAMS;
// IDENTIFY DEVICE 回傳資料 (512 bytes, 256 WORDs) - 節錄關鍵欄位
typedef struct _IDINFO {
USHORT wGenConfig, wNumCyls, wReserved2, wNumHeads, wReserved4, wReserved5, wNumSectorsPerTrack;
USHORT wVendorUnique[3];
CHAR sSerialNumber[20]; // WORD 10-19
USHORT wBufferType, wBufferSize, wECCSize;
CHAR sFirmwareRev[8]; // WORD 23-26
CHAR sModelNumber[40]; // WORD 27-46
// ... 能力位元組、時序、版本、UltraDMA 支援位元組 ...
} IDINFO;
關鍵對齊提醒:WORD 57-58 (CHS 可尋址扇區數) 不滿足 32-bit 對齊,不可定義為單一 ULONG,需拆為 USHORT wCurSectorsLow, wCurSectorsHigh,否則結構偏移錯誤導致資料解析失敗 (Lynn McGuire 原版即犯此錯)。
HANDLE OpenDevice(LPCTSTR filename) {
return CreateFile(filename, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
}
BOOL IdentifyDevice(HANDLE hDevice, PIDINFO pIdInfo) {
PSENDCMDINPARAMS pSCIP = (PSENDCMDINPARAMS)GlobalAlloc(LMEM_ZEROINIT, sizeof(SENDCMDINPARAMS)-1);
PSENDCMDOUTPARAMS pSCOP = (PSENDCMDOUTPARAMS)GlobalAlloc(LMEM_ZEROINIT, sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1);
pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
pSCIP->cBufferSize = 0;
pSCOP->cBufferSize = sizeof(IDINFO);
DWORD dwOut;
BOOL b = DeviceIoControl(hDevice, DFP_RECEIVE_DRIVE_DATA,
pSCIP, sizeof(SENDCMDINPARAMS)-1,
pSCOP, sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1,
&dwOut, NULL);
memcpy(pIdInfo, pSCOP->bBuffer, sizeof(IDINFO));
GlobalFree(pSCOP); GlobalFree(pSCIP);
return b;
}
BOOL GetPhysicalDriveInfoInNT(int nDrive, PIDINFO pIdInfo) {
char szFile[20]; sprintf(szFile, "\\\\.\\PhysicalDrive%d", nDrive);
HANDLE h = OpenDevice(szFile); if (h==INVALID_HANDLE_VALUE) return FALSE;
BOOL b = IdentifyDevice(h, pIdInfo);
if (b) { AdjustString(pIdInfo->sSerialNumber,20); AdjustString(pIdInfo->sModelNumber,40); AdjustString(pIdInfo->sFirmwareRev,8); }
CloseHandle(h); return b;
}
BOOL IdentifyDeviceAsScsi(HANDLE hDevice, int nDrive, PIDINFO pIdInfo) {
PSRB_IO_CONTROL pSRBIO = (PSRB_IO_CONTROL)GlobalAlloc(LMEM_ZEROINIT,
sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1);
PSENDCMDINPARAMS pSCIP = (PSENDCMDINPARAMS)((BYTE*)pSRBIO+sizeof(SRB_IO_CONTROL));
PSENDCMDOUTPARAMS pSCOP = (PSENDCMDOUTPARAMS)((BYTE*)pSRBIO+sizeof(SRB_IO_CONTROL));
pSRBIO->HeaderLength = sizeof(SRB_IO_CONTROL);
pSRBIO->Timeout = 10000;
pSRBIO->Length = sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1;
pSRBIO->ControlCode = IOCTL_SCSI_MINIPORT_IDENTIFY;
strncpy((char*)pSRBIO->Signature, "SCSIDISK", 8);
pSCIP->irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
pSCIP->bDriveNumber = nDrive;
DWORD dwOut;
BOOL b = DeviceIoControl(hDevice, IOCTL_SCSI_MINIPORT,
pSRBIO, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDINPARAMS)-1,
pSRBIO, sizeof(SRB_IO_CONTROL)+sizeof(SENDCMDOUTPARAMS)+sizeof(IDINFO)-1,
&dwOut, NULL);
memcpy(pIdInfo, pSCOP->bBuffer, sizeof(IDINFO));
GlobalFree(pSRBIO);
return b;
}
BOOL GetIdeDriveAsScsiInfoInNT(int nDrive, PIDINFO pIdInfo) {
// nDrive: 0=PriMaster, 1=PriSlave, 2=SecMaster, 3=SecSlave
char szFile[20]; sprintf(szFile, "\\\\.\\Scsi%d:", nDrive/2);
HANDLE h = OpenDevice(szFile); if (h==INVALID_HANDLE_VALUE) return FALSE;
BOOL b = IdentifyDeviceAsScsi(h, nDrive%2, pIdInfo);
if (b && pIdInfo->sModelNumber[0]!='\0') {
AdjustString(pIdInfo->sSerialNumber,20); AdjustString(pIdInfo->sModelNumber,40); AdjustString(pIdInfo->sFirmwareRev,8);
}
CloseHandle(h); return b;
}
void AdjustString(char* str, int len) {
for (int i=0; i=0 && str[i]==' ') str[i--]='\0'; // 右修剪
}
| 路徑 | 需求權限 | 適用對象 | 備註 |
|---|---|---|---|
| PhysicalDrive (IDE 直通) | Administrator / SYSTEM | IDE/ATA/SATA 硬碟 | 最直接,資訊最全 |
| ScsiX: (SCSI Miniport) | 一般使用者即可 | IDE/ATA/SATA (經由 SCSI 層轉譯) | 繞過 ACL,但 Win 未來版本可能封鎖 |
IDENTIFY PACKET DEVICE (0xA1) 用於 ATAPI 裝置 (光碟機、ZIP、磁帶機),但驅動程式常不支援透傳,實務上難以取得資訊。IOCTL_SCSI_PASS_THROUGH、IOCTL_STORAGE_QUERY_PROPERTY)。DiskID32 範例 (使用 VXD/APM)。ATA 指令集包含
READ SECTORS、WRITE SECTORS、SECURITY ERASE、SLEEP等具破壞性命令。若擴充此框架發送寫入/安全命令,務必三思,錯誤操作可能導致資料永久遺失或硬碟損壞。
掌握 DeviceIoControl + IDENTIFY DEVICE 模式,即可在應用層建立硬碟資訊檢測、健康監控、資產管理等實用工具。