zoukankan      html  css  js  c++  java
  • 实战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/

  • 相关阅读:
    02.jwt单点登录
    04.RBAC
    COM interop
    C++、c#互调用之VC6 调用 VC6 COM
    Type Library Importer (Tlbimp.exe)
    C++、C#互调用之C++ 调用C# dll
    VS tools
    Type Library to Assembly 转换摘要
    7个顶级心理预言
    c++、C#互调用之c# 调用 vc6 COM
  • 原文地址:https://www.cnblogs.com/lzjsky/p/1947874.html
Copyright © 2011-2022 走看看