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到设备路径的代码如下:
在头文件中添加 :#include <SetupAPI.h>
在VS属性->配置属性->链接器->输入->附加依赖项 中添加 setupapi.lib
// 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 对这两个问题我也是心存疑惑,但又不敢妄加猜测,正琢磨着向微软请教呢。