当前位置: 首页 > ai >正文

实战 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.h,setupapi.h,以及连接 setupapi.lib。

 

Q: 得到设备路径后,就可以到下一步,用CreateFile打开设备,然后用DeviceIoControl 进行读写了吧?

A: 是的。尽管该设备路径与以前我们接触的那些不太一样。本是“.PhysicalDrive0”,现在鸟枪换炮,

  变成了类似这样的一副尊容:

   “?ide#diskmaxtor_2f040j0__________________________vam51jj0#31465634475345582020202

   02020202020202020#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}”。

  其实这个 设备名在注册表的某处可以找到,例如在Win2000中这个名字位于 HKEY_LOCAL_MACHINESystemCurrentControlSetServicesDiskEnum,只不过“#”换成了“”。分析 一下这样的设备路径,你会发现很有趣的东西,它们是由接口类型、产品型号、固件版本、序列号、计算机名、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返回的数据中,没有得到CDROM和USB接口的外置硬盘的序列号、产品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 发声?

http://www.xdnf.cn/news/10919.html

相关文章:

  • Android jetpack最佳总结和实践
  • Windows CE 模拟器和远程调试工具
  • 按钮提交表单
  • 关于MySQL的文献M_什么是参考文献?文献类型标识码有,M、J、C、N、D、P、S、DB/OL、J/OL、N/OL等它们都代表什么意思?(提...
  • 中标麒麟服务器系统安装教程,安装国产Linux中标麒麟操作系统教程
  • 提升交际能力:8个技巧避免尬聊,寻找话题轻松聊天
  • 怎么完全卸载赛门铁克_赛门铁克(sep)卸载方法
  • 技术人如何搭建自己的技术博客
  • ARP病毒全析
  • Linux——sort命令详细使用方法
  • 新手学编程必会的100个代码
  • FR-E840-K 第二加减速使用方法
  • 网站流量统计分析
  • 你也能轻松破解CNKI 密码
  • QSetting
  • MySQL--内连接查询(inner join)
  • ARM嵌入式
  • OpenGL入门系列- 编程指南例子解析 opengl_wrap
  • 游戏引擎列表
  • 爬虫入门练习
  • 在线拍卖系统(竞拍系统)源码编写开发中拍卖模式的理解和实现
  • 安卓app 实现监听手机app全局弹窗消息通知功能
  • git stash介绍(贮藏、暂存)(临时保存当前工作目录中尚未提交的修改)
  • 蓝桥杯国赛训练 day1
  • 6.4 note
  • c++ algorithm
  • 互联网 Web 网站
  • flex布局实现固定区域滚动
  • 贝利特价型号SPHSS03 液压伺服模块
  • maven在修改完配置之后新建项目还是不生效的原因