Q 前幾次我們討論的都是設(shè)備名比較清楚的情況,有了設(shè)備名(路徑),就可以直接調(diào)用CreateFile打開設(shè)備,進行它所支持的I/O操作了。如果事先并不能確切知道設(shè)備名,如何去訪問設(shè)備呢?
A 訪問設(shè)備必須用設(shè)備句柄,而得到設(shè)備句柄必須知道設(shè)備路徑,這個套路以你我之力是改變不了的。每個設(shè)備都有它所屬類型的GUID,我們順著這個GUID就能獲得設(shè)備路徑。
GUID是同類或同種設(shè)備的全球唯一識別碼,它是一個128 bit(16字節(jié))的整形數(shù),真實面目為
typedef struct _GUID{ unsigned long Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[8];} GUID, *PGUID;
例如,Disk類的GUID為“53f56307-b6bf-11d0-94f2-00a0c91efb8b”,在我們的程序里可以定義為
const GUID DiskClassGuid = {0x53f56307L, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b)};
或者用一個宏來定義
DEFINE_GUID(DiskClassGuid, 0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
通過GUID找出設(shè)備路徑,需要用到一組設(shè)備管理的API函數(shù)
SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, SetupDiGetInterfaceDeviceDetail, SetupDiDestroyDeviceInfoList,
以及結(jié)構(gòu)SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA。
有關(guān)信息請查閱MSDN,這里就不詳細(xì)介紹了。
實現(xiàn)GUID到設(shè)備路徑的代碼如下:
// SetupDiGetInterfaceDeviceDetail所需要的輸出長度,定義足夠大#define INTERFACE_DETAIL_SIZE (1024) // 根據(jù)GUID獲得設(shè)備路徑// lpGuid: GUID指針// pszDevicePath: 設(shè)備路徑指針的指針// 返回: 成功得到的設(shè)備路徑個數(shù),可能不止1個int GetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath){ HDEVINFO hDevInfoSet; SP_DEVICE_INTERFACE_DATA ifdata; PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail; int nCount; BOOL bResult; // 取得一個該GUID相關(guān)的設(shè)備信息集句柄 hDevInfoSet = ::SetupDiGetClassDevs(lpGuid, // class GUID NULL, // 無關(guān)鍵字 NULL, // 不指定父窗口句柄 DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // 目前存在的設(shè)備 // 失敗... if (hDevInfoSet == INVALID_HANDLE_VALUE) { return 0; } // 申請設(shè)備接口數(shù)據(jù)空間 pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT, INTERFACE_DETAIL_SIZE); pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); nCount = 0; bResult = TRUE; // 設(shè)備序號=0,1,2... 逐一測試設(shè)備接口,到失敗為止 while (bResult) { ifdata.cbSize = sizeof(ifdata); // 枚舉符合該GUID的設(shè)備接口 bResult = ::SetupDiEnumDeviceInterfaces( hDevInfoSet, // 設(shè)備信息集句柄 NULL, // 不需額外的設(shè)備描述 lpGuid, // GUID (ULONG)nCount, // 設(shè)備信息集里的設(shè)備序號 &ifdata); // 設(shè)備接口信息 if (bResult) { // 取得該設(shè)備接口的細(xì)節(jié)(設(shè)備路徑) bResult = SetupDiGetInterfaceDeviceDetail( hDevInfoSet, // 設(shè)備信息集句柄 &ifdata, // 設(shè)備接口信息 pDetail, // 設(shè)備接口細(xì)節(jié)(設(shè)備路徑) INTERFACE_DETAIL_SIZE, // 輸出緩沖區(qū)大小 NULL, // 不需計算輸出緩沖區(qū)大小(直接用設(shè)定值) NULL); // 不需額外的設(shè)備描述 if (bResult) { // 復(fù)制設(shè)備路徑到輸出緩沖區(qū) ::strcpy(pszDevicePath[nCount], pDetail->DevicePath); // 調(diào)整計數(shù)值 nCount++; } } } // 釋放設(shè)備接口數(shù)據(jù)空間 ::GlobalFree(pDetail); // 關(guān)閉設(shè)備信息集句柄 ::SetupDiDestroyDeviceInfoList(hDevInfoSet); return nCount;}
調(diào)用GetDevicePath函數(shù)時要注意,pszDevicePath是個指向字符串指針的指針,例如可以這樣
int i; char* szDevicePath[MAX_DEVICE]; // 設(shè)備路徑 // 分配需要的空間 for (i = 0; i < MAX_DEVICE; i++) { szDevicePath[i] = new char[256]; } // 取設(shè)備路徑 nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath); // 逐一獲取設(shè)備信息 for (i = 0; i < nDevice; i++) { // 打開設(shè)備 hDevice = ::OpenDevice(szDevicePath[i]); if (hDevice != INVALID_HANDLE_VALUE) { ... ... // I/O操作 ::CloseHandle(hDevice); } } // 釋放空間 for (i = 0; i & lt; MAX_DEVICE; i++) { delete []szDevicePath[i]; }
本例的Project中除了要包含winioctl.h外,還要包含initguid.h,setupapi.h,以及連接setupapi.lib。
Q 得到設(shè)備路徑后,就可以到下一步,用CreateFile打開設(shè)備,然后用DeviceIoControl進行讀寫了吧?
A 是的。盡管該設(shè)備路徑與以前我們接觸的那些不太一樣。本是“\\.\PhysicalDrive0”,現(xiàn)在鳥槍換炮,變成了類似這樣的一副尊容:
“\\?\ide#diskmaxtor_2f040j0__________________________vam51jj0#3146563447534558202020202020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”。
其實這個設(shè)備名在注冊表的某處可以找到,例如在Win2000中這個名字可以位于
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Disk\Enum\0,
只不過“#”換成了“\”。分析一下這樣的設(shè)備路徑,你會發(fā)現(xiàn)很有趣的東西,它們是由接口類型、產(chǎn)品型號、固件版本、序列號、計算機名、GUID等信息組合而成的。當(dāng)然,它是沒有規(guī)范的,不能指望從這里面得到你希望知道的東西。
用CreateFile打開設(shè)備后,對于存儲設(shè)備,IOCTL_DISK_GET_DRIVE_GEOMETRY,IOCTL_STORAGE_GET_MEDIA_TYPES_EX等I/O控制碼照常使用。
今天我們討論一個新的控制碼:IOCTL_STORAGE_QUERY_PROPERTY,獲取設(shè)備屬性信息,希望得到系統(tǒng)中所安裝的各種固定的和可移動的硬盤、優(yōu)盤和CD/DVD-ROM/R/W的接口類型、序列號、產(chǎn)品ID等信息。
// IOCTL控制碼#define IOCTL_STORAGE_QUERY_PROPERTY CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)// 存儲設(shè)備的總線類型typedef enum _STORAGE_BUS_TYPE { BusTypeUnknown = 0x00, BusTypeScsi, BusTypeAtapi, BusTypeAta, BusType1394, BusTypeSsa, BusTypeFibre, BusTypeUsb, BusTypeRAID, BusTypeMaxReserved = 0x7F} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE; // 查詢存儲設(shè)備屬性的類型typedef enum _STORAGE_QUERY_TYPE { PropertyStandardQuery = 0, // 讀取描述 PropertyExistsQuery, // 測試是否支持 PropertyMaskQuery, // 讀取指定的描述 PropertyQueryMaxDefined // 驗證數(shù)據(jù)} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE; // 查詢存儲設(shè)備還是適配器屬性typedef enum _STORAGE_PROPERTY_ID { StorageDeviceProperty = 0, // 查詢設(shè)備屬性 StorageAdapterProperty // 查詢適配器屬性} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID; // 查詢屬性輸入的數(shù)據(jù)結(jié)構(gòu)typedef struct _STORAGE_PROPERTY_QUERY { STORAGE_PROPERTY_ID PropertyId; // 設(shè)備/適配器 STORAGE_QUERY_TYPE QueryType; // 查詢類型 UCHAR AdditionalParameters[1]; // 額外的數(shù)據(jù)(僅定義了象征性的1個字節(jié))} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY; // 查詢屬性輸出的數(shù)據(jù)結(jié)構(gòu)typedef struct _STORAGE_DEVICE_DESCRIPTOR { ULONG Version; // 版本 ULONG Size; // 結(jié)構(gòu)大小 UCHAR DeviceType; // 設(shè)備類型 UCHAR DeviceTypeModifier; // SCSI-2額外的設(shè)備類型 BOOLEAN RemovableMedia; // 是否可移動 BOOLEAN CommandQueueing; // 是否支持命令隊列 ULONG VendorIdOffset; // 廠家設(shè)定值的偏移 ULONG ProductIdOffset; // 產(chǎn)品ID的偏移 ULONG ProductRevisionOffset; // 產(chǎn)品版本的偏移 ULONG SerialNumberOffset; // 序列號的偏移 STORAGE_BUS_TYPE BusType; // 總線類型 ULONG RawPropertiesLength; // 額外的屬性數(shù)據(jù)長度 UCHAR RawDeviceProperties[1]; // 額外的屬性數(shù)據(jù)(僅定義了象征性的1個字節(jié))} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR; // 取設(shè)備屬性信息// hDevice -- 設(shè)備句柄// pDevDesc -- 輸出的設(shè)備描述和屬性信息緩沖區(qū)指針(包含連接在一起的兩部分)BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc){ STORAGE_PROPERTY_QUERY Query; // 查詢輸入?yún)?shù) DWORD dwOutBytes; // IOCTL輸出數(shù)據(jù)長度 BOOL bResult; // IOCTL返回值 // 指定查詢方式 Query.PropertyId = StorageDeviceProperty; Query.QueryType = PropertyStandardQuery; // 用IOCTL_STORAGE_QUERY_PROPERTY取設(shè)備屬性信息 bResult = ::DeviceIoControl(hDevice, // 設(shè)備句柄 IOCTL_STORAGE_QUERY_PROPERTY, // 取設(shè)備屬性信息 &Query, sizeof(STORAGE_PROPERTY_QUERY), // 輸入數(shù)據(jù)緩沖區(qū) pDevDesc, pDevDesc->Size, // 輸出數(shù)據(jù)緩沖區(qū) &dwOutBytes, // 輸出數(shù)據(jù)長度 (LPOVERLAPPED)NULL); // 用同步I/O return bResult;}
Q 我用這個方法從IOCTL_STORAGE_QUERY_PROPERTY返回的數(shù)據(jù)中,沒有得到CDROM和USB接口的外置硬盤的序列號、產(chǎn)品ID等信息。但從設(shè)備路徑上看,明明是有這些信息的,為什么它沒有填充到STORAGE_DEVICE_DESCRIPTOR中呢?再就是為什么硬盤序列號本是“D22P7KHE ”,為什么它填充的是“3146563447534558202020202020202020202020”這種形式呢?
A 對這兩個問題我也是心存疑惑,但又不敢妄加猜測,正琢磨著向微軟請教呢。