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

2024-01-14 09:32

本文主要是介绍实战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 & 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/


From: http://blog.csdn.net/bhw98/archive/2003/05/27/19664.aspx

这篇关于实战DeviceIoControl 之五:列举已安装的存储设备的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/604712

相关文章

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也

2025版mysql8.0.41 winx64 手动安装详细教程

《2025版mysql8.0.41winx64手动安装详细教程》本文指导Windows系统下MySQL安装配置,包含解压、设置环境变量、my.ini配置、初始化密码获取、服务安装与手动启动等步骤,... 目录一、下载安装包二、配置环境变量三、安装配置四、启动 mysql 服务,修改密码一、下载安装包安装地

Redis MCP 安装与配置指南

《RedisMCP安装与配置指南》本文将详细介绍如何安装和配置RedisMCP,包括快速启动、源码安装、Docker安装、以及相关的配置参数和环境变量设置,感兴趣的朋友一起看看吧... 目录一、Redis MCP 简介二、安www.chinasem.cn装 Redis MCP 服务2.1 快速启动(推荐)2.

在macOS上安装jenv管理JDK版本的详细步骤

《在macOS上安装jenv管理JDK版本的详细步骤》jEnv是一个命令行工具,正如它的官网所宣称的那样,它是来让你忘记怎么配置JAVA_HOME环境变量的神队友,:本文主要介绍在macOS上安装... 目录前言安装 jenv添加 JDK 版本到 jenv切换 JDK 版本总结前言China编程在开发 Java

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例

《PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例》词嵌入解决NLP维度灾难,捕捉语义关系,PyTorch的nn.Embedding模块提供灵活实现,支持参数配置、预训练及变长... 目录一、词嵌入(Word Embedding)简介为什么需要词嵌入?二、PyTorch中的nn.Em

在IntelliJ IDEA中高效运行与调试Spring Boot项目的实战步骤

《在IntelliJIDEA中高效运行与调试SpringBoot项目的实战步骤》本章详解SpringBoot项目导入IntelliJIDEA的流程,教授运行与调试技巧,包括断点设置与变量查看,奠定... 目录引言:为良驹配上好鞍一、为何选择IntelliJ IDEA?二、实战:导入并运行你的第一个项目步骤1