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

时间: 2024-08-28 20:29:34

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

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

Q 前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用CreateFile打开设备,进行它所支持的I/O操作了.如果事先并不能确切知道设备名,如何去访问设备呢? A 访问设备必须用设备句柄,而得到设备句柄必须知道设备路径,这个套路以你我之力是改变不了的.每 个设备都有它所属类型的GUID,我们顺着这个GUID就能获得设备路径. GUID是同类或同种设备的全球唯一识别码,它是一个128 bit(16字节)的整形数,真实面目为 typedef struct _GUID {

MongoDB实战(1)MongoDB安装与存储结构

一.linux平台的安装 wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz tar -zxvf mongodb-linux-x86_64-2.4.8.tgz #创建数据存放目录和日志目录 /data/db /data/log/mongo.log #启动mongod服务进程 --fork后台运行 /mongodb/bin/mongod --dbpath=/data/db --logpath=/data/log/m

实战DeviceIoControl系列之一:通过API访问设备驱动程序

Q 在 NT/2000/XP 中,我想用 VC 编写应用程序访问硬件设备,如获取磁盘参数.读写绝对扇区数据.测试光驱实际速度等,该从哪里入手呢? A 在 NT/2000/XP 中,应用程序可以通过 API 函数 DeviceIoControl 来实现对设备的访问-获取信息,发送命令,交换数据等.利用该接口函数向指定的设备驱动发送正确的控制码及数据,然后分析它的响应,就可以达到我们的目的. DeviceIoControl 的函数原型为 BOOL DeviceIoControl( HANDLE hD

使用 Yum 历史查找已安装或已删除的软件包信息

Yum 是 RHEL/CentOS 的一个基于 rpm 的交互式高级包管理器,用户可以用它来安装新的软件包.卸载或清除旧的/不需要的软件包.它可以自动运行系统更新,并执行依赖分析,对已安装的或可用的软件包进行查询等等. 在本文中,我们将解释如何查看 Yum 事务的历史记录,以便于了解有关安装的软件包以及从系统中所卸载/清除软件包的信息. 推荐阅读: 20 条关于 Linux 软件包管理的 Yum 命令 以下是一些如何使用 Yum 历史命令的示例. 查看完整的 Yum 历史 要查看 Yum 事务完

使用Yum历史查找已安装或已删除的软件包信息

Yum 是 RHEL/CentOS 的一个基于 rpm 的交互式高级包管理器,用户可以用它来安装新的软件包.卸载或清除旧的/不需要的软件包.它可以自动运行系统更新,并执行依赖分析,对已安装的或可用的软件包进行查询等等 在本文中,我们将解释如何查看 Yum 事务的历史记录,以便于了解有关安装的软件包以及从系统中所卸载/清除软件包的信息. 推荐阅读: 20 条关于 Linux 软件包管理的 Yum 命令 以下是一些如何使用 Yum 历史命令的示例. 查看完整的 Yum 历史 要查看 Yum 事务完整

如何查看Win8 Metro已安装软件的大小

  Win8的两大特点就是Metro和Windows Store,一个带给了我们新鲜的界面,另一个带给了我们无穷尽的软件应用,但是由于Win8的平板界面不支持使用一右键查看文件大小,因此我们不知道已安装的软件到底占据了多少磁盘空间,这就给管理应用造成了麻烦.有什么方法可以查看应用大小呢?这里有一个小妙招,大家不妨一起来试试. 1.首先,通过右侧的Charm栏进入"更改电脑设置",点选"常规"选项. 2.其次,在"常规"中,找到"可用存储

巧妙查看Win8 Metro已安装软件的大小

  1.首先,通过右侧的Charm栏进入"更改电脑设置",点选"常规"选项. 2.其次,在"常规"中,找到"可用存储",这里会显示目前你电脑上的空闲空间.点击"查看应用大小"就能看到已安装应用占据的硬盘空间量了. 只要通过上面的两步,你就可以查看到所有应用软件所占用的空间体积,在这些应用中,你肯定能发现一些"体积"巨大但并不常用的应用软件,选择卸载就可以节省了我们的空间.这个小小的功能对

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

检测Office是否安装以及获取安装 路径 及安装版本  代码如下 复制代码     #region 检测Office是否安装      ///<summary>      /// 检测是否安装office      ///</summary>      ///<param name="office_Version"> 获得并返回安装的office版本</param>      ///<returns></returns

《Linux KVM虚拟化架构实战指南》——2.2 安装配置RHEV虚拟化所需服务器

2.2 安装配置RHEV虚拟化所需服务器 RHEV虚拟化架构需要DNS.RHEL等服务器支持,本节将搭建一个完整的RHEV虚拟化实战环境. 2.2.1 配置RHEV虚拟化所需服务器一:活动目录服务器 在RHEV虚拟化环境下,DNS服务器相当重要,可以通过安装配置1台Windows服务器或Linux服务器来实现DNS解析功能.由于RHEV虚拟化实战操作会涉及AD活动目录,所以本节的实战操作将使用Windows Server 2008 R2构建AD活动目录服务器,同时提供DNS.DHCP等功能,关于