实战DeviceIoControl 之五:列举已安装的存储设备 - bhw98的专栏 - CSDNBlog

导读:
   Q前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用CreateFile打开设备,进行它所支持的I/O操作了。如果事先并不能确切知道设备名,如何去访问设备呢?
   A访问设备必须用设备句柄,而得到设备句柄必须知道设备路径,这个套路以你我之力是改变不了的。每个设备都有它所属类型的GUID,我们顺着这个GUID就能获得设备路径。
  GUID是同类或同种设备的全球唯一识别码,它是一个128 bit(16字节)的整形数,真实面目为typedefstruct_GUID
  {
  unsignedlongData1;
  unsignedshortData2;
  unsignedshortData3;
  unsignedcharData4[8];
  } GUID, *PGUID;
  
  例如,Disk类的GUID为“53f56307-b6bf-11d0-94f2-00a0c91efb8b”,在我们的程序里可以定义为constGUID 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所需要的输出长度,定义足够大
  #defineINTERFACE_DETAIL_SIZE (1024)
  
  // 根据GUID获得设备路径
  // lpGuid: GUID指针
  // pszDevicePath: 设备路径指针的指针
  // 返回: 成功得到的设备路径个数,可能不止1个
  intGetDevicePath(LPGUID lpGuid, LPTSTR* pszDevicePath)
  {
  HDEVINFO hDevInfoSet;
  SP_DEVICE_INTERFACE_DATA ifdata;
  PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
  intnCount;
  BOOL bResult;
  
  // 取得一个该GUID相关的设备信息集句柄
  hDevInfoSet = ::SetupDiGetClassDevs(lpGuid, // class GUID
  NULL, // 无关键字
  NULL, // 不指定父窗口句柄
  DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // 目前存在的设备
  
  // 失败...
  if(hDevInfoSet == INVALID_HANDLE_VALUE)
  {
  return0;
  }
  
  // 申请设备接口数据空间
  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);
  
  returnnCount;
  }
  
  调用GetDevicePath函数时要注意,pszDevicePath是个指向字符串指针的指针,例如可以这样inti;
  char* szDevicePath[MAX_DEVICE]; // 设备路径
  
  // 分配需要的空间
  for(i = 0;i   {
  szDevicePath[i] = newchar[256];
  }
  
  // 取设备路径
  nDevice = ::GetDevicePath((LPGUID)&DiskClassGuid, szDevicePath);
  
  // 逐一获取设备信息
  for(i = 0;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.h,setupapi.h,以及连接setupapi.lib。
   Q得到设备路径后,就可以到下一步,用CreateFile打开设备,然后用DeviceIoControl进行读写了吧?
   A是的。尽管该设备路径与以前我们接触的那些不太一样。本是“//./PhysicalDrive0”,现在鸟枪换炮,变成了类似这样的一副尊容:
  “//?/ide#diskmaxtor_2f040j0__________________________vam51jj0#3146563447534558202020202020202020202020#{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控制码
  #defineIOCTL_STORAGE_QUERY_PROPERTY CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)
  // 存储设备的总线类型
  typedefenum_STORAGE_BUS_TYPE {
  BusTypeUnknown = 0x00,
  BusTypeScsi,
  BusTypeAtapi,
  BusTypeAta,
  BusType1394,
  BusTypeSsa,
  BusTypeFibre,
  BusTypeUsb,
  BusTypeRAID,
  BusTypeMaxReserved = 0x7F
  } STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
  
  // 查询存储设备属性的类型
  typedefenum_STORAGE_QUERY_TYPE {
  PropertyStandardQuery = 0, // 读取描述
  PropertyExistsQuery, // 测试是否支持
  PropertyMaskQuery, // 读取指定的描述
  PropertyQueryMaxDefined // 验证数据
  } STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE;
  
  // 查询存储设备还是适配器属性
  typedefenum_STORAGE_PROPERTY_ID {
  StorageDeviceProperty = 0, // 查询设备属性
  StorageAdapterProperty // 查询适配器属性
  } STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID;
  
  // 查询属性输入的数据结构
  typedefstruct_STORAGE_PROPERTY_QUERY {
  STORAGE_PROPERTY_ID PropertyId; // 设备/适配器
  STORAGE_QUERY_TYPE QueryType; // 查询类型
  UCHAR AdditionalParameters[1]; // 额外的数据(仅定义了象征性的1个字节)
  } STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
  
  // 查询属性输出的数据结构
  typedefstruct_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
  
  returnbResult;
  }
  
   Q我用这个方法从IOCTL_STORAGE_QUERY_PROPERTY返回的数据中,没有得到CDROM和USB接口的外置硬盘的序列号、产品ID等信息。但从设备路径上看,明明是有这些信息的,为什么它没有填充到STORAGE_DEVICE_DESCRIPTOR中呢?再就是为什么硬盘序列号本是“D22P7KHE ”,为什么它填充的是“3146563447534558202020202020202020202020”这种形式呢?
   A对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。
   [相关资源]
  本文Demo源码:StorageEnum.zip(23KB)
  bhw98的专栏:http://www.csdn.net/develop/author/netauthor/bhw98/
  

本文转自
http://blog.csdn.net/bhw98/archive/2003/05/27/19664.aspx
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值