分享
 
 
 

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

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

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 前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用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 & lt; 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控制码

#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 对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。

[相关资源]

本文Demo源码:StorageEnum.zip (23KB)

bhw98的专栏:http://www.csdn.net/develop/author/netauthor/bhw98/

首次发布:2003-02-28

最后修订:2003-05-20

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有