实战 DeviceIoControl 系列 之五:列举已安装的存储设备

本文介绍如何利用DeviceIoControl函数通过GUID获取存储设备路径,并演示如何使用IOCTL_STORAGE_QUERY_PROPERTY控制码来查询各种存储设备的属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实战 DeviceIoControl 之五:列举已安装的存储设备


Q
前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用CreateFile打开
设备,进行它所支持的I/O操作了。如果事先并不能确切知道设备名,如何去访问设备呢?
A
访问设备必须用设备句柄,而得到设备句柄必须知道设备路径,这个套路以你我之力是改变不了的。
每个设备都有它所属类型的GUID,我们顺着这个GUID就能获得设备路径。
GUID
是同类或同种设备的全球唯一识别码,它是一个128 bit(16字节)的整形数,真实面目为
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找出设备路径,需要用到一组设备管理的API函数
SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, SetupDiGetInterfaceDeviceDetail,
SetupDiDestroyDeviceInfoList, 
以及结构SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA
有关信息请查阅MSDN,这里就不详细介绍了。 实现GUID到设备路径的代码如下:
// SetupDiGetInterfaceDeviceDetail
所需要的输出长度,定义足够大
#define INTERFACE_DETAIL_SIZE (1024)
//
根据GUID获得设备路径
// lpGuid: GUID
指针
// pszDevicePath:
设备路径指针的指针
//
返回: 成功得到的设备路径个数,可能不止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相关的设备信息集句柄
 hDevInfoSet = ::SetupDiGetClassDevs(lpGuid,   // class GUID  
  NULL,     //
无关键字 
  NULL,     //
不指定父窗口句柄 
  DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); //
目前存在的设备
 //
失败...
 if(hDevInfoSet == INVALID_HANDLE_VALUE)
 {
  return 0;
 }
 //
申请设备接口数据空间
 pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT,
INTERFACE_DETAIL_SIZE);
 
 pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
 nCount = 0;
 bResult = TRUE;
 
 //
设备序号=0,1,2... 逐一测试设备接口,到失败为止
 while (bResult)
 {
  ifdata.cbSize=sizeof(ifdata);
 
  //
枚举符合该GUID的设备接口
  bResult = ::SetupDiEnumDeviceInterfaces(    hDevInfoSet,  //
设备信息集句柄
   NULL,   //
不需额外的设备描述
   lpGuid,   // GUID
   (ULONG)nCount,  //
设备信息集里的设备序号
   &ifdata);  //
设备接口信息
  if(bResult)
  {
   //
取得该设备接口的细节(设备路径)
   bResult = SetupDiGetInterfaceDeviceDetail(
    hDevInfoSet,  //
设备信息集句柄
    &ifdata,  //
设备接口信息
    pDetail,  //
设备接口细节(设备路径)
    INTERFACE_DETAIL_SIZE, //
输出缓冲区大小
    NULL,   //
不需计算输出缓冲区大小(直接用设定值)
    NULL);   //
不需额外的设备描述
   if(bResult)
   {
    //
复制设备路径到输出缓冲区
    ::strcpy(pszDevicePath[nCount], pDetail->DevicePath);
    //
调整计数值
    nCount++;
   }
  }
 }
 //
释放设备接口数据空间
 ::GlobalFree(pDetail);
 //
关闭设备信息集句柄
 ::SetupDiDestroyDeviceInfoList(hDevInfoSet);
 return nCount;
}
调用GetDevicePath函数时要注意,pszDevicePath是个指向字符串指针的指针,例如可以这样
 int i;
 char* szDevicePath[MAX_DEVICE];  //
设备路径
 //
分配需要的空间
 for(i=0; i<MAX_DEVICE; i++) szDevicePath[i] = new char[256];  //
取设备路径
 nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);
 //
逐一获取设备信息
 for(i=0; i<nDevice; i++)
 {
  //
打开设备
  hDevice = ::OpenDevice(szDevicePath[i]);
  if(hDevice != INVALID_HANDLE_VALUE)
  {
   ... ...  // I/O
操作
  ::CloseHandle(hDevice);
  }
 }
 //
释放空间
 for(i=0;i<MAX_DEVICE;i++) delete []szDevicePath[i];
本例的Project中除了要包含winioctl.h外,还要包含initguid.hsetupapi.h,以及连接setupapi.lib
 
Q
得到设备路径后,就可以到下一步,用CreateFile打开设备,然后用DeviceIoControl 进行读写了吧?
A
是的。尽管该设备路径与以前我们接触的那些不太一样。本是“////.//PhysicalDrive0”,现在鸟枪换炮,
变成了类似这样的一副尊容:
“////?//ide#diskmaxtor_2f040j0__________________________vam51jj0#31465634475345582020202
02020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”

其实这个设备名在注册表的某处可以找到,例如在Win2000中这个名字位于
HKEY_LOCAL_MACHINE//System//CurrentControlSet//Services//Disk//Enum//0

只不过“#”换成了“//”。分析一下这样的设备路径,你会发现很有趣的东西,它们是由接口类型、产品
型号、固件版本、序列号、计算机名、GUID等信息组合而成的。当然,它是没有规范的,不能指望从这
里面得到你希望知道的东西。
CreateFile打开设备后,对于存储设备,IOCTL_DISK_GET_DRIVE_GEOMETRY
IOCTL_STORAGE_GET_MEDIA_TYPES_EX
I/O控制码照常使用。
今天我们讨论一个新的控制码:IOCTL_STORAGE_QUERY_PROPERTY,获取设备属性信息,希望得到
系统中所安装的各种固定的和可移动的硬盘、优盘和CD/DVD-ROM/R/W的接口类型、序列号、产品ID
等信息。
// IOCTL
控制码
#define IOCTL_STORAGE_QUERY_PROPERTY   CTL_CODE(IOCTL_STORAGE_BASE, 0x0500,
METHOD_BUFFERED, FILE_ANY_ACCESS) //
存储设备的总线类型
typedef enum _STORAGE_BUS_TYPE {
     BusTypeUnknown = 0x00,
     BusTypeScsi,
     BusTypeAtapi,
     BusTypeAta,
     BusType1394,
     BusTypeSsa,
     BusTypeFibre,
     BusTypeUsb,
     BusTypeRAID,
     BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
//
查询存储设备属性的类型
typedef enum _STORAGE_QUERY_TYPE {
     PropertyStandardQuery = 0,           //
读取描述
     PropertyExistsQuery,                 //
测试是否支持
     PropertyMaskQuery,                   //
读取指定的描述
     PropertyQueryMaxDefined  //
验证数据
} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
//
查询存储设备还是适配器属性
typedef enum _STORAGE_PROPERTY_ID {
     StorageDeviceProperty = 0,   //
查询设备属性
     StorageAdapterProperty   //
查询适配器属性
} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
//
查询属性输入的数据结构
typedef struct _STORAGE_PROPERTY_QUERY {
 STORAGE_PROPERTY_ID PropertyId;  //
设备/适配器
 STORAGE_QUERY_TYPE QueryType;  //
查询类型 
     UCHAR AdditionalParameters[1];  //
额外的数据(仅定义了象征性的1个字节)
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
 
//
查询属性输出的数据结构
typedef struct _STORAGE_DEVICE_DESCRIPTOR {
     ULONG Version;  //
版本
     ULONG Size;   //
结构大小
     UCHAR DeviceType;  //
设备类型
     UCHAR DeviceTypeModifier; // SCSI-2
额外的设备类型
     BOOLEAN RemovableMedia; //
是否可移动
     BOOLEAN CommandQueueing; //
是否支持命令队列
     ULONG VendorIdOffset;  //
厂家设定值的偏移      ULONG ProductIdOffset;  // 产品ID的偏移
     ULONG ProductRevisionOffset; //
产品版本的偏移
     ULONG SerialNumberOffset;  //
序列号的偏移
     STORAGE_BUS_TYPE BusType;  //
总线类型
     ULONG RawPropertiesLength;  //
额外的属性数据长度
     UCHAR RawDeviceProperties[1]; //
额外的属性数据(仅定义了象征性的1个字节)
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
 
//
取设备属性信息
// hDevice --
设备句柄
// pDevDesc --
输出的设备描述和属性信息缓冲区指针(包含连接在一起的两部分)
BOOL GetDriveProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc)
{
 STORAGE_PROPERTY_QUERY Query; //
查询输入参数
 DWORD dwOutBytes;    // IOCTL
输出数据长度
 BOOL bResult;     // IOCTL
返回值
 //
指定查询方式
 Query.PropertyId = StorageDeviceProperty;
 Query.QueryType = PropertyStandardQuery;
 //
IOCTL_STORAGE_QUERY_PROPERTY取设备属性信息
 bResult = ::DeviceIoControl(hDevice,  //
设备句柄
  IOCTL_STORAGE_QUERY_PROPERTY,  //
取设备属性信息
  &Query, sizeof(STORAGE_PROPERTY_QUERY), //
输入数据缓冲区
  pDevDesc, pDevDesc->Size,  //
输出数据缓冲区
  &dwOutBytes,    //
输出数据长度
  (LPOVERLAPPED)NULL);   //
用同步I/O 
 return bResult;
}
 
Q
我用这个方法从IOCTL_STORAGE_QUERY_PROPERTY返回的数据中,没有得到CDROMUSB
接口的外置硬盘的序列号、产品ID等信息。但从设备路径上看,明明是有这些信息的,为什么它没有填充
STORAGE_DEVICE_DESCRIPTOR中呢?再就是为什么硬盘序列号本是“D22P7KHE            ”,为
什么它填充的是“3146563447534558202020202020202020202020”这种形式呢?
A
对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。 
 
P.bhw98
{
PADDING-RIGHT: 0px;
PADDING-LEFT: 0px;
FONT-SIZE: 9pt;
PADDING-BOTTOM: 0px;
MARGIN: 10px 0px 5px;
LINE-HEIGHT: normal;
PADDING-TOP: 0px;
FONT-FAMILY: Verdana, Arial
}
PRE.bhw98
{
FONT-SIZE: 9pt;
PADDING-RIGHT: 5px;
PADDING-LEFT: 5px;
PADDING-BOTTOM: 5px;
MARGIN: 5px 0px;
LINE-HEIGHT: normal;
PADDING-TOP: 5px;
BACKGROUND-COLOR: #f0f0f0
}
PRE.diag
{
FONT-SIZE: 9pt;
PADDING-RIGHT: 5px;
PADDING-LEFT: 5px;
PADDING-BOTTOM: 5px;
MARGIN: 5px 0px;
LINE-HEIGHT: normal;
PADDING-TOP: 5px;
}
CODE.bhw98
{
FONT-SIZE: 9pt;
COLOR: #000000
}
TABLE.bhw98
{
BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid;
FONT-SIZE: 9pt;
MARGIN: 3px 0px 10px;
BORDER-LEFT: #808080 1px solid;
LINE-HEIGHT: normal;
BORDER-BOTTOM: #808080 1px solid;
FONT-FAMILY: Verdana, Arial
}
TD.bhw98
{
BORDER-RIGHT: darkgray 1px solid;
PADDING-RIGHT: 10px;
BORDER-TOP: darkgray 1px solid;
PADDING-LEFT: 5px;
FONT-SIZE: 9pt;
PADDING-BOTTOM: 0px;
MARGIN: 0px;
BORDER-LEFT: darkgray 1px solid;
LINE-HEIGHT: normal;
PADDING-TOP: 3px;
BORDER-BOTTOM: darkgray 1px solid;
FONT-FAMILY: Verdana, Arial;
BACKGROUND-COLOR: #f0f0f0
}
STRONG.bhw98
{
FONT-WEIGHT: bolder;
FONT-SIZE: 20pt;
COLOR: #228b22;
FONT-STYLE: italic;
FONT-FAMILY: Verdana, Arial
}
LI.bhw98
{
FONT-SIZE: 9pt;
MARGIN: 3px 0px 0px 3px;
LINE-HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
H1.bhw98
{
MARGIN-TOP: 25px;
FONT-WEIGHT: bolder;
FONT-SIZE: 12pt; MARGIN-BOTTOM: 5px;
LINE-HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
H2.bhw98
{
MARGIN-TOP: 20px;
FONT-WEIGHT: bolder;
FONT-SIZE: 10.5pt;
MARGIN-BOTTOM: 5px;
LINE-HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
H3.bhw98
{
MARGIN-TOP: 15px;
FONT-WEIGHT: bolder;
FONT-SIZE: 9pt;
MARGIN-BOTTOM: 5px;
LINE-HEIGHT: normal;
FONT-FAMILY: Verdana, Arial
}
SPAN.key
{
COLOR: #0000ff
}
SPAN.num
{
COLOR: #800000
}
SPAN.str
{
COLOR: #8b008b
}
SPAN.rem
{
COLOR: #008000
}
Q
NT/2000/XP 中,如何读取 CMOS 数据?
Q
NT/2000/XP 中,如何控制 speaker 发声? 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值