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. Date对象和正则对象

    1.Date对象 创建 var date1 = new Date(); var date2 = new Date(12983798123);//填一个毫秒值,应该是距离1970年1月1日.....多少 ...

  2. HTML5之Notification简单使用

    var webNotification = { init: function() { if(!this.isSupport()) { console.log('不支持通知'); return; } t ...

  3. vue项目实现记住密码到cookie功能(附源码)

    实现功能: 1.记住密码勾选,点登陆时,将账号和密码保存到cookie,下次登陆自动显示到表单内 2.不勾选,点登陆时候则清空之前保存到cookie的值,下次登陆需要手动输入 大体思路就是通过存/取/ ...

  4. ABP官方文档翻译 7.3 Quartz集成

    Quartz集成 介绍 安装 创建Jobs 计划安排Jobs 更多 介绍 Quartz是一个全功能的.开源的job计划安排系统,可以用在小的apps也可以用于大型的企业系统.Abp.Quartz包简化 ...

  5. ABP官方文档翻译 4.5 特征管理

    特征管理 介绍 关于IFeatureValueStore 特征类型 Boolean特征 Value特征 定义特征 基本特征属性 其他特征属性 特征层级 检查特征 使用RequiresFeature特性 ...

  6. Django搭建博客网站(四)

    Django搭建博客网站(四) 最后一篇主要讲讲在后台文章编辑加入markdown,已经在文章详情页对markdown的解析. Django搭建博客网站(一) Django搭建博客网站(二) Djan ...

  7. Flink入门使用

    完全参考:Flink1.3QuickStart 启动本地运行 首先找一台安装了hadoop的linux. 将安装包解压,到bin目录启动local模式的脚本. tar -zxvf flink-1.3. ...

  8. 在 Mac 中安装 MySQLdb (Python mysql )

    安装环境:OS X操作系统,Python 2.7.3. MySQLdb其实包含在MySQL-python包中,因此无论下载还是在pip中search,都应该是搜寻MySQL-python. 以下将说明 ...

  9. angularjs 控制器、作用域、广播详解

    一.控制器 首先列出几种我们平常使用控制器时的几种误区: 我们知道angualrJs中一个控制器时可以对应不同的视图模板的,但这种实现方式存在的问题是: 如果视图1和视图2根本没有任何逻辑关系,这样& ...

  10. 洛谷P3390【模板】矩阵快速幂——矩阵运算入门笔记

    作为一个因为极度畏惧数学 而选择成为一名OIer的蒟蒻 终于还是迎来了要面对的这一天 一般题目中矩阵运算好像只用到矩阵乘法 (或许只是蒟蒻我做的题太少) 而且矩阵的乘法也是较难理解的一部分 所以就简单 ...