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}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章