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

2023-11-09

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

[相关资源]

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Typescript 类可以实现可调用接口吗?

    Typescript 接口允许定义函数风格的调用签名 因此 interface A x number number 这可以通过例如一个函数 const a A function x number number return 1 是否可以使用
  • 方法的通用接口重载?

    有没有一种好的 通用的方法来执行以下操作 而无需诉诸第二种方法或大量强制转换 我希望 API 尽可能轻量 并且在 OO 方面对我来说似乎没问题 class Foo public T Bar
  • 将 null 分配给角度 4 中的接口

    我有一个 Angular4 界面 interface StatusDetail statusName string name string 我已经给它分配了一些值 比如 角度分量 export class EditComponent imp
  • 何时使用 Dart 中的接口?

    我正在阅读 Dart 的文档 对于如何使用接口 我有点困惑 也许是因为我来自 Ruby 当然 接口并不是 Dart 所独有的 关于何时应该使用接口有很多解释 This one https stackoverflow com question
  • 如何通过反射忽略父接口上的事件来获取类型的事件

    我有以下代码 Type type var events type GetEvents BindingFlags DeclaredOnly BindingFlags Instance BindingFlags Public ToList 然而
  • Typescript 为具有动态和静态键的对象创建接口

    我正在尝试学习打字稿 但在界面方面遇到了障碍 我有一个想要保存的对象token和一个route如下 const obj token thisismytoken path to somewhere 我在这里遇到的问题是 如何生成该对象的接口
  • 在 C# 中实现接口与显式实现接口 [重复]

    这个问题在这里已经有答案了 我在 VS2010 中有两个用于实现接口的选项 当我有 IHelper cs 界面如下 public interface IHelper IEnumerable
  • DTO 接口

    我目前正在开始开发一个大型 Web 应用程序 主要包含 Angular SPA 和可以访问后端层的 OData WebAPI 我们正处于早期阶段 并已开始实施第一批课程 包括Model dll它位于公共名称空间中 以便所有层都可以访问它 我
  • 如何获取给定 PIDL 的 IWMDMStorageControl 接口

    我使用 SHBrowseForFolder 选择 MTP 设备上的文件夹 然后我想从那里复制文件 到那里复制文件 IWMDMStorageControl 接口 来自 Windows Media Format 11 SDK 似乎很合适 但是如
  • Java 接口中的可选方法

    根据我的理解 如果你在java中实现一个接口 那么该接口中指定的方法必须由实现该接口的子类使用 我注意到在某些接口 例如 Collection 接口 中 有些方法被注释为可选 但这到底意味着什么 它让我有点困惑 因为我认为接口中指定的所有方
  • 如何将依赖项注入到实现接口的类中?

    我知道接口不能定义构造函数 强制所有类实现接口并以统一契约接收其依赖项的最佳实践是什么 我知道整数可以通过属性将依赖项注入到对象中 但通过构造函数传递它们对我来说更有意义 那么如何DI呢 我知道你说过你想要一份稳定的合同 但有一个优势not
  • IEnumerable 如何在后台工作

    我正在徘徊于更深入的功能IEnumerable
  • Java 中的泛型

    我想了解以下类型的语法 Example public interface A lt T extends A lt T gt gt 这个接口的逻辑是什么 这将按如下方式使用 class X implements A
  • Golang:我可以投射到 chan 接口吗{}

    我正在尝试为订阅编写一个通用包装器 例如 type Subscriber interface Subscribe addr string chan interface 假设有一个我想使用的库 其中有一个 subscribe 方法 但它使用c
  • 如何自动生成已实现接口的方法

    PhpStorm 中是否有一种方法可以自动生成给定类正在实现的接口所需的空方法 假设我们有一个包含 3 个方法的接口 当定义实现该接口的新类时 一些选项可以为所有必需的方法自动生成容器 你当然可以 有 3 种方法可以做到这一点 Press
  • 如何在 Java 中以编程方式获取接口的所有实现的列表?

    我可以通过反思或类似的方式来做到这一点吗 我已经搜索了一段时间 似乎有不同的方法 这里总结一下 反思 https github com ronmamo reflections如果您不介意添加依赖项 该库非常受欢迎 它看起来像这样 Refle
  • typescript 扩展不需要的接口

    我有两个接口 interface ISuccessResponse Success boolean Message string and interface IAppVersion extends ISuccessResponse OSVe
  • 通用接口列表

    如果我有一个带有几个实现类的通用接口 例如 public interface IDataElement
  • 数据源和数据集的区别

    我目前正在开发一个项目 其主要任务是读取存储在 SQL 数据库中的数据并以用户友好的形式显示它们 使用的编程语言是C 我在 Borland C Builder 6 环境中工作 但我认为标题中提出的问题与编程语言或库无关 当从数据库读取数据时
  • 有没有办法找到哪些 .NET 类实现了某个接口?

    例如 如果我想查看我的 NET 选项用于实现 IList 或 IDictionary 有没有办法找到它 例如在 MSDN 文档中 我认为可以使用反射器 http www red gate com products reflector

随机推荐

  • 制作esp32-cam拍照上传,微信小程序照片显示的监控小车

    前期配件准备 ESP32 CAM开发环境配置 程序下载 连接小车 控制台查看图片 微信小程序的开发 1 前期配件准备 小车套件 网上购买即可 较为方便的选择 ESP32 CAM 推荐安信可 外加TTL下载器 方便烧录程序 L298N马达驱动
  • User-agent

    1 手机User Agent 更多关于手机User Agent请 点击 UPPOOL Mozilla 5 0 Linux U Android 8 1 0 zh cn BLA AL00 Build HUAWEIBLA AL00 AppleWe
  • 盘点

    选自Github 机器之心编译 hunkim 盘点了 Github 上 18 个深度学习项目 根据收藏数自动排名 最新的一次 update 在几小时前完成 项目地址 https github com hunkim DeepLearningS
  • sh: 1: pause: not found

    linux下运行c 程序时 希望控制台不会输出后马上消失 在windows系统下 用如下语句 include
  • redis-benchmark 基准测试

    redis benchmark 基准测试 大家好 我是爱吃鱼的程序员 一个渴望在互联网行业做到C位的程序员 可柔可刚 点赞则柔 白嫖则刚 看完记得给我来个三连哦 欢迎私信 1 概述 当我们希望提高性能的使用 往往想到的是异步 缓存这个两种手
  • nginx部署的时候出现403错误

    nginx部署的时候出现403错误 原因 启动用户和nginx工作用户不一致所致 解决方法 1 先查看当前的启动用户 命令 ps aux grep nginx worker process awk print 1 这样就是启动用户和工作用户
  • Linux下多线程调试的一些方法

    一直觉得Linux下的多线程调试是很麻烦的 因为一般大一点的程序线程会很多 通过gdb的info thread命令看全都是系统调用 看不到详细的方法 至少我看到是这样的 如果用thread id跟进每个thread去bt 是件相当痛苦的事情
  • 关于spring-boot中的@SpringBootApplication中的@ComponentScan的basePackages的路劲的设置。...

    最近在看spring boot的东西 然后搭建了spring boot的简单项目 但是在spring的入口处加载的时候一直加载不到 最后看了 SpringBootApplication的源码才知道 ComponentScan 如果不设置ba
  • ChatGPT教程:如何优化我们编写的Python代码?

    背景介绍 作为一名程序员 我们经常需要编写Python代码 然而 代码质量的好坏直接关系到程序的可读性 可维护性和可扩展性 因此 我们需要使用一些工具来帮助我们提高代码质量 ChatGPT是一种强大的自然语言处理模型 可以帮助我们完成这项任
  • 全面接入:ChatGPT杀进15个商业应用,让AI替你打工

    智东西 智能产业新媒体 智东西专注报道人工智能主导的前沿技术发展 和技术应用带来的千行百业产业升级 聚焦智能变革 服务产业升级 ChatGPT狂飙160天 世界已经不是两个月前的样子 文 李水青 编辑 心缘 来源 智东西 ID zhidxc
  • findbug类型

    Summary Description Category BC Equals method should not assume anything about the type of its argument Bad practice BIT
  • 十九、SpringAOP切面的单例与多例

    Repository Scope prototype public class UserDao implements IUserDao public void save System out println 保存成功 无返回值 Config
  • 硬件基础——滤波

    一 滤波概念 滤波是将信号中特定波段频率滤除的操作 是抑制和防止干扰的一项重要措施 滤波器分类 1 当允许信号中较高频率的成分通过滤波器时 这种滤波器叫做高通滤波器 2 当允许信号中较低频率的成分通过滤波器时 这种滤波器叫做低通滤波器 3
  • vue echarts 三维折线图

    效果图
  • USR-WIFI232-B2(WIFI)模块没有和服务器TCP连接成功时,单片机读取USR-WIFI232-B2(WIFI)模块的MAC地址

    您想要实现什么功能 1 单片机上电后先读取WIFI模块的MAC地址 2 读取完WIFI模块的MAC地址的后 WIFI模块和上位机进行TCP通信 WIFI模块作为服务器 需要发送 a 进入AT指令配置状态 读取MAC 读取之后 发送AT EN
  • 用HL7创建含多个code item的modality worklist

    需求 DCM4CHEE做RIS DICOM服务器 用NHAPI发ORM o01消息创建worklist 问题 在同一个OBR里面没法包含多个Scheduled Protocol Code Sequence item 创建出来的worklis
  • kafka介绍

    一 kafka介绍 1 1 kafka 模型介绍 Kafka 是一个开源的 轻量级的 支持多分区 多副本 基于 Zookeeper 的分布式消息流平台 相比于其它消息系统 其有以下优点 支持发布订阅模式的消息引擎系统 储存数据流时提供容错机
  • 直观了解CNN

    CNN解释器 https poloclub github io cnn explainer GitHub https github com poloclub cnn explainer 论文 https arxiv org abs 2004
  • Buck电路的参数计算及仿真

    一 Buck电路的参数计算较为简单 可以用matlab来完成 代码如下 clear clc Vin 12 输入电压单位V Vout 5 输出电压单位V Fs 100000 开关频率单位Hz DeltaIL 0 25 电流纹波单位A Delt
  • 实战DeviceIoControl 之五:列举已安装的存储设备

    Q 前几次我们讨论的都是设备名比较清楚的情况 有了设备名 路径 就可以直接调用CreateFile打开设备 进行它所支持的I O操作了 如果事先并不能确切知道设备名 如何去访问设备呢 A 访问设备必须用设备句柄 而得到设备句柄必须知道设备路