Windows驅動之SetupDi系列函數

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中,提供給後續的函數遍歷。

可以遍歷兩種類別:

  1. Device Setup Class Control Options
  2. Device Interface Class Control Options。

這個函數返回的結果信息如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LhivjTKg-1584015865065)(assets/images/2019-10-21-16-12-23.png)]

其中 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內部也是通過這個函數來實現的):

  1. SetupDiCreateDeviceInfoList : 創建設備集合。
  2. 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. 整個原理

  1. 註冊表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e97d-e325-11ce-bfc1-08002be10318}獲取設備安裝類的枚舉信息。
  2. 通過實例獲取到實例id。
  3. 獲取HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum下面設備相關信息。
  4. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class:是設備安裝類GUID,代表當前設備類型。
  5. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses:設備接口類GUID
  6. 通過註冊表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}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章