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”这种形式呢?

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

  1. ubuntu安装和查看已安装

    说明:由于图形化界面方法(如Add/Remove... 和Synaptic Package Manageer)比较简单,所以这里主要总结在终端通过命令行方式进行的软件包安装.卸载和删除的方法. 一.U ...

  2. asp.net中通过注册表来检测是否安装Office(迅雷/QQ是否已安装)

    原文  asp.net中通过注册表来检测是否安装Office(迅雷/QQ是否已安装) 检测Office是否安装以及获取安装 路径 及安装版本  代码如下 复制代码 #region 检测Office是否 ...

  3. 插件化开发—动态加载技术加载已安装和未安装的apk

    首先引入一个概念,动态加载技术是什么?为什么要引入动态加载?它有什么好处呢?首先要明白这几个问题,我们先从 应用程序入手,大家都知道在Android App中,一个应用程序dex文件的方法数最大不能超 ...

  4. UWP DEP0700: 应用程序注册失败。[0x80073CF9] 另一个用户已安装此应用的未打包版本。当前用户无法将该版本替换为打包版本。

    最近电脑抽风,我在[应用程序和功能]中重置了以下我的App自然灾害,居然,搞出大新闻了. 它居然从列表中消失了... vs再次编译代码的时候,提示 严重性 代码 说明 项目 文件 行 禁止显示状态 错 ...

  5. (转)Linux-HA实战(1)— Heartbeat安装

    原文:http://blog.csdn.net/liaomin416100569/article/details/76087448-------centos7源代码编译安装heartbeat 原文:h ...

  6. linux中安装软件,查看、卸载已安装软件方法

    各种主流Linux发行版都采用了某种形式的包管理系统(PMS)来控制软件和库的安装. 软件包存储在服务器上,可以利用本地Linux系统上的PMS工具通过互联网访问.这些服务器称为仓库. 由于Linux ...

  7. LAMP架构应用实战—Apache服务介绍与安装01

    LAMP架构应用实战—Apache服务介绍与安装01   一:Apache是什么 Apache是Apache基金会开发的一个高性能.功能强大.安全可靠.灵活的开放源码的WEB服务软件 二:Apache ...

  8. [Python3网络爬虫开发实战] 1.7.2-mitmproxy的安装

    mitmproxy是一个支持HTTP和HTTPS的抓包程序,类似Fiddler.Charles的功能,只不过它通过控制台的形式操作. 此外,mitmproxy还有两个关联组件,一个是mitmdump, ...

  9. 插件化开发—动态载入技术载入已安装和未安装的apk

    首先引入一个概念,动态载入技术是什么?为什么要引入动态载入?它有什么优点呢?首先要明确这几个问题.我们先从 应用程序入手,大家都知道在Android App中.一个应用程序dex文件的方法数最大不能超 ...

随机推荐

  1. C# 取值函数

    C# 中取绝对值的函数 System.Math.Abs(float value); System.Math.Abs(decimal value);System.Math.Abs(int value); ...

  2. 程序员之殇 —— (Are you afraid of me? Don't be.)灵感=神秘感

    Are you afraid of me? (你们怕我吗?) Don't be.(不用怕) I am a programmer who just won't die.(我是不会死的程序员) 自从跟踪到 ...

  3. c# excel print 打印 将所有列调整为一页

    excel有时候列数比较多,行数也比较多,转换成xps文档的时候,一般是通过打印来实现. 由于打印的范围限制,所以会出现本来在一行的数据,由于列数比较多,溢出范围,被打印到两页了. 为解决这个问题,需 ...

  4. SpringMVC源码情操陶冶-DispatcherServlet简析(二)

    承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求 DispatcherServlet#doDi ...

  5. POJ 3590 The shuffle Problem [置换群 DP]

    传送门 $1A$太爽了 从此$Candy?$完全理解了这种$DP$做法 和bzoj1025类似,不过是求最大的公倍数,并输出一个字典序最小的方案 依旧枚举质因子和次数,不足的划分成1 输出方案从循环长 ...

  6. pug新手学习

    感觉自己有段时间没有跟新了,唉,只是一直找不到可以写的必要 其实我一直对pug特别感兴趣的,安装点我 记得全局安装pug和pug-cli就行了 在webstorm配环境记得在前面加-P a: img  ...

  7. 关于字符型char变量

    写程序时,意外发现个很不容易察觉问题出在哪的问题的 scanf("%c",&ch); scanf("%c",&c); printf(" ...

  8. gitlab项目迁移

    ALL Git* => Gitlab Nothing, Just copy the git URL to gitlab(类似于 fork) 使用 Git Mirror 無痛轉移 Git Serv ...

  9. Centos7安装GitLab

    GitLab CE Download Archives gitlab安装调试小记 Gitlab Free Trial GitLab搭建手记 Gitlab社区版的使用 GUI PNG Gitlab升级到 ...

  10. zabbix客户端一键安装脚本

    #!/bin/bash #通过命令行参数指定if [ ! -z "$1" ];then ip=$1 echo "手动指定IP:$ip"else#根据默认路由获取 ...