文章目錄
SetupDi系列函數進行設備信息的管理
1. SetupDiGetClassDevs
1.1 枚舉設備信息
HDEVINFO SetupDiGetClassDevs(
_In_opt_ const GUID *ClassGuid,
_In_opt_ PCTSTR Enumerator,
_In_opt_ HWND hwndParent,
_In_ DWORD Flags
);
SetupDiGetClassDevs
獲取一個指定類別或全部類別的所有已安裝設備的信息。
1.2 參數說明
ClassGuid
: 一個特定類別GUID(需要查詢註冊表)的指針,如果設置了DIGCF_ALLCLASSES
標記,該參數備忽略,將返回所有類別的設備信息表(這裏的GUID有設備安裝類的GUID和設備接口類的GUID)。
Enumerator
: 過濾枚舉的內容:如:PCI則只顯示PCI設備,
hwndParent
: 用於關聯到集合成員中的用戶接口的頂層窗口句柄
Flags
: 建立設備信息表的控制選項,可以是下列值
DIGCF_ALLCLASSES
範圍最廣:對全部的任何設備類或接口類有支持的設備;DIGCF_DEVICEINTERFACE
:僅包含支持設備接口類的設備;DIGCF_DEFAULT
:僅包含支持默認接口類的設備;DIGCF_PRESENT
:當前連接的設備;DIGCF_PROFILE
:Hardware Profile中的設備,要看哪些設備在硬件Profile中,應到註冊表鍵HKLM/SYSTEM/CurrentControlSet/Hardware Profiles/Current/System/CurrentControlSet
下查看。
1.3 返回值
如成功,返回包含所有與指定參數匹配的已經安裝設備信息句柄
如失敗則返回INVALID_HANDLE_VALUE
1.4 原理
主要是從HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum
(如果是接口類,那麼通過HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
獲取)枚舉到所有的信息,然後獲取到ClassGUID
,然後根據這個值過濾參數。其中,這裏主要根據Flags的不同,然後枚舉不同的註冊表。
只有當Flags指定爲DIGCF_DEVICEINTERFACE
,纔會從HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
讀取相關信息。
過濾到結果之後,將結果放入到一個全局鏈表DeviceInfoSet
中,提供給後續的函數遍歷。
可以遍歷兩種類別:
- Device Setup Class Control Options
- Device Interface Class Control Options。
這個函數返回的結果信息如下:
其中 DeviceInfoSet /* HDEVINFO */
結構如下,有一個LIST_ENTRY ListHead;
鏈接DeviceInfo
,如下:
struct DeviceInfoSet /* HDEVINFO */
{
DWORD magic; /* SETUP_DEVICE_INFO_SET_MAGIC */
GUID ClassGuid;
HKEY HKLM;
HMACHINE hMachine;
SP_DEVINSTALL_PARAMS_W InstallParams;
LIST_ENTRY DriverListHead;
LIST_ENTRY ListHead; //DeviceInfo
struct DeviceInfo *SelectedDevice;
struct ClassInstallParams ClassInstallParams;
HMODULE hmodClassPropPageProvider;
PVOID pClassPropPageProvider;
PCWSTR MachineName;
WCHAR szData[ANYSIZE_ARRAY];
};
DeviceInfo
的結構信息如下:
struct DeviceInfo /* Element of DeviceInfoSet.ListHead */
{
LIST_ENTRY ListEntry;
DEVINST dnDevInst;
struct DeviceInfoSet *set;
SP_DEVINSTALL_PARAMS_W InstallParams;
PCWSTR instanceId;
PCWSTR UniqueId;
PCWSTR DeviceDescription;
GUID ClassGuid;
DWORD CreationFlags;
LIST_ENTRY DriverListHead; /* List of struct DriverInfoElement */
LIST_ENTRY InterfaceListHead; /* List of struct DeviceInterface */
struct ClassInstallParams ClassInstallParams;
HMODULE hmodDevicePropPageProvider;
PVOID pDevicePropPageProvider;
WCHAR Data[ANYSIZE_ARRAY];
};
DeviceInterface
設備接口的結構信息如下:
struct DeviceInterface /* Element of DeviceInfo.InterfaceListHead */
{
LIST_ENTRY ListEntry;
struct DeviceInfo *DeviceInfo;
GUID InterfaceClassGuid;
DWORD Flags;
WCHAR SymbolicLink[ANYSIZE_ARRAY];
};
SetupDiGetClassDevs
獲取設備信息的集合,通過如下兩個接口也可以達到同樣的目的(其實SetupDiGetClassDevs
內部也是通過這個函數來實現的):
SetupDiCreateDeviceInfoList
: 創建設備集合。SetupDiCreateDeviceInfo
: 創建設備信息。
2. SetupDiEnumDeviceInfo
2.1 枚舉成員
BOOL SetupDiEnumDeviceInfo(
_In_ HDEVINFO DeviceInfoSet,
_In_ DWORD MemberIndex,
_Out_ PSP_DEVINFO_DATA DeviceInfoData
);
SetupDiEnumDeviceInfo
枚舉指定設備信息集合的成員,並將數據放在PSP_DEVINFO_DATA
中
2.2 參數說明
DeviceInfoSet
: 提供一個設備信息集合的句柄
MemberIndex
: 指定一個要取得的設備信息成員序號,從0開始
DeviceInfoData
: 指向SP_DEVINFO_DATA
結構的指針,關於指定成員的返回信息就放在該結構中
typedef struct _SP_DEVINFO_DATA {
DWORD cbSize;
GUID ClassGuid;
DWORD DevInst;
ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
2.3 返回值
成功返回True,否則返回False
如果要枚舉全部設備信息成員,裝載者首先應該將MemberIndex
設爲0調用SetupDiEnumDeviceInfo
,然後遞增MemberIndex
(使用一個for循環),調用SetupDiEnumDeviceInfo
,直至所有成員全部遍歷(此時函數返回False,並且GetLastError
返回ERROR_NO_MORE_ITEMS
)。
2.4 原理
枚舉DeviceInfoSet
鏈表中的元素。
3. SetupDiEnumDeviceInterfaces
3.1 枚舉接口
上面的函數用來枚舉“設備信息集合”中的設備,對應地,還有一個函數用來枚舉集合中的設備接口。
WINSETUPAPI BOOL WINAPI
SetupDiEnumDeviceInterfaces(
IN HDEVINFO DeviceInfoSet, // 設備信息集合
IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL// 設備成員
IN LPGUID InterfaceClassGuid, // 接口GUID
IN DWORD MemberIndex, // 接口在集合中的Index
OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData// 返回的接口信息
);
DeviceInfoSet
是“設備信息集合”變量,即SetupDiGetClassDevs
調用的返回值。
DeviceInfoData
值很少有人會設置,但如果設置了此值,則函數枚舉過程中,將只枚舉集合中DeviceInfoData包含的設備接口類。打比方說,DeviceInfoData
所代表的設備支持GUID1和GUID2兩個設備接口類,則函數在集合中將僅尋找這兩類設備接口,而不管其他。
由於此函數用來獲取設備接口,而一個集合設置同一個設備中,可包含多種設備接口類,所以參數InterfaceClassGuid
用來設置設備接口GUID。
MemberIndex
意義與前相同,調用時應將其初始值設置爲0,循環調用,直到MemberIndex值超出集合範圍使得SetupDiEnumDeviceInterfaces
返回FALSE,可結束遞增循環。
DeviceInterfaceData
中返回設備接口信息。
typedef struct _SP_DEVICE_INTERFACE_DATA {
DWORD cbSize;
GUID InterfaceClassGuid;
DWORD Flags;
ULONG_PTR Reserved;
} SP_DEVICE_INTERFACE_DATA, *PSP_DEVICE_INTERFACE_DATA;
3.2 獲取接口數據
BOOL SetupDiGetDeviceInterfaceDetail(
_In_ HDEVINFO DeviceInfoSet,
_In_ PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
_Out_opt_ PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData,
_In_ DWORD DeviceInterfaceDetailDataSize,
_Out_opt_ PDWORD RequiredSize,
_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData
);
typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA {
DWORD cbSize;
TCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;
4. SetupDiGetDeviceRegistryProperty
4.1 設備屬性
BOOL SetupDiGetDeviceRegistryProperty(
_In_ HDEVINFO DeviceInfoSet,
_In_ PSP_DEVINFO_DATA DeviceInfoData,
_In_ DWORD Property,
_Out_opt_ PDWORD PropertyRegDataType,
_Out_opt_ PBYTE PropertyBuffer,
_In_ DWORD PropertyBufferSize,
_Out_opt_ PDWORD RequiredSize
);
SetupDiGetDeviceRegistryProperty
:函數用來檢索指定的即插即用設備特性(這個函數主要是使用System\CurrentControlSet\Enum註冊表來查詢相關硬件信息)
4.2 參數說明
DeviceInfoSet
: 設備信息句柄。
DeviceInfoData
: SP_DEVINFO_DATA
結構體,包含DeviceInfoSet
中的設備信息
Property
取以下的值:
SPDRP_ADDRESS
: 查詢設備的地址SPDRP_BUSNUMBER
: 查詢設備的bus號SPDRP_BUSTYPEGUID
: 查詢設備的GUID號- … …
4.3 備註
這個函數的主要原理是:從註冊表中讀取PnP設備的屬性。
5. SetupDiDestroyDeviceInfoList
5.1 銷燬
SetupDiDestroyDeviceInfoList
銷燬一個設備信息集合,並且釋放所有關聯的內存
BOOL SetupDiDestroyDeviceInfoList( HDEVINFO DeviceInfoSet );
5.2 參數
DeviceInfoSet
: 要釋放的設備信息句柄
5.3 返回值
成功返回非零,否則返回零
6. 整個原理
- 註冊表
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e97d-e325-11ce-bfc1-08002be10318}
獲取設備安裝類的枚舉信息。 - 通過實例獲取到實例id。
- 獲取
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum
下面設備相關信息。 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
:是設備安裝類GUID,代表當前設備類型。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
:設備接口類GUID- 通過註冊表
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
獲取設備接口的枚舉信息。
6.1 設備接口類
任何一種驅動都要爲用戶,或者其他使用者提供某種類型的名字。而使用者也通過這種名字來區別不同的設備,並與之進行IO。
在Windows NT 4.0和以前的版本中,驅動程序會爲設備對象創建名字,併爲之創建設備連接符,並將之註冊到系統中。
自Windows 2000開始,驅動程序不再爲設備對象命名,而是使用設備接口類。設備接口類可以向使用者提供設備和驅動程序函數的入口,驅動將註冊設備接口類,併爲設備對象創建設備接口類實例。
每個設備接口類都有一個GUID。系統在device-specific header files中定義了常用設備類與其GUID。但設備開發者可以自定義設備類。
例如,三種不同類型的鼠標可以屬於同一個設備接口類,即使他們分別使用了USB口,串口,紅外端口。他們的驅動都把他註冊爲GUID_DEVINTERFACE_MOUSE
設備接口類,這個設備接口類的GUID在Ntddmou.h中定義。
特別指出,這些驅動只註冊了一個設備類。然而,各種設備的驅動可以有專門的函數以註冊除標準接口類以外的接口類,如可安裝磁盤驅動必須註冊(GUID_DEVINTERFACE_DISK
) 接口類以及(MOUNTDEV_MOUNTED_DEVICE_GUID
)可安裝設備類。
當驅動註冊了設備接口的實例後,IO管理器就將設備和設備接口的GUID,符號鏈接名聯繫在一起。符號鏈接名存儲在註冊表中,在系統啓動時即存在。使用某用戶可以查詢這個接口以獲得支持這個接口的設備的符號鏈接名。使用者可以用這個符號連接名來對設備進行IO.
6.2 設備安裝類
爲了便於設備的安裝,具有同種安裝方式的設備被歸爲同一個設備安裝類,例如 SCSI 媒體轉換設備被歸類到MediumChanger device setup class中,設備安裝類中定義了在設備安裝過程中設備安裝程序與其相關程序所使用到的類。
微軟爲大部分設備定義了標準設備安裝類,設備開發者可以自定義設備安裝類,但必須是在標準設備安裝類都不能使用的情況下。
每個設備安裝類都有一個GUID,系統在Devguid.h給出了各設備安裝類的GUID,並且給定了相應的符號連接名。
設備安裝類爲屬於自己的設備定義了..\CurrentControlSet\Control\Class\ClassGuid
下的一個子鍵。
開發者可以通過INF文件來創建一個新的設備安裝類。
6.3 設備接口類的註冊
NTSTATUS
IoRegisterDeviceInterface(
IN PDEVICE_OBJECT PhysicalDeviceObject,
IN CONST GUID *InterfaceClassGuid,
IN PUNICODE_STRING ReferenceString OPTIONAL,
OUT PUNICODE_STRING SymbolicLinkName
);
InterfaceClassGuid
這個指定了接口類的GUID,如果指定了一個新有的GUID,那麼,將在設備接口類註冊表中新生成一個新的表項,如下:
DEFINE_GUID(IOM_RING_INTERFACE,
0xfdcac3d6, 0xe85f, 0x4f22, 0xa2, 0x3c, 0x75, 0xe2, 0xbd, 0xef, 0x7f, 0x62);
nStatus = IoRegisterDeviceInterface(PhysicalDeviceObject, &IOM_RING_INTERFACE, NULL, &pDeviceExtension->InterfaceName);
那麼生成的註冊表項爲HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{fdcac3d6-e85f-4f22-a23c-75e2bdef7f62}
。
7. 實例
下面提供一個代碼,查詢能夠打開工作的USB的設備接口:
DEFINE_GUID (UsbClassGuid, 0xa5dcbf10L, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed);
int _tmain(int argc, _TCHAR* argv[])
{
HDEVINFO hDevInfo;
SP_DEVICE_INTERFACE_DATA spDevData;
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
BOOL bRes = TRUE;
int nCount = 0;
hDevInfo = ::SetupDiGetClassDevs(NULL,NULL,NULL,DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (hDevInfo != INVALID_HANDLE_VALUE)
{
pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT,1024);
pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
while (bRes)
{
spDevData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
bRes = ::SetupDiEnumDeviceInterfaces(hDevInfo,NULL,(LPGUID)&UsbClassGuid,nCount,&spDevData);
if (bRes)
{
bRes = ::SetupDiGetInterfaceDeviceDetail(hDevInfo,&spDevData,pDetail,1024,NULL,NULL);
if (bRes)
{
wcout << L"success : " << pDetail->DevicePath << endl;
nCount ++;
}
}
}
::GlobalFree(pDetail);
::SetupDiDestroyDeviceInfoList(hDevInfo);
}
return 0;
}
這個代碼得到的返回結果如下:
success : \\?\usb#vid_24ae&pid_1813#5&262ed807&1&3#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
success : \\?\usb#vid_0bda&pid_58dd#200901010001#{a5dcbf10-6530-11d2-901f-00c04fb951ed}