最近碰到了个新问题,记录下来作为windows的磁盘操作那个系列的续篇吧。
一些时候我们的程序需要区分本地存储设备和USB
存储设备。在网上搜一搜一般会找到一个最直接的API
,
GetDriveType
,其原型为
UINT GetDriveType(LPCTSTR lpRootPathName)
参数
lpRootPathName
是存储设备的根目录,例如C:\
,返回值即为设备类型。
Return code
|
Description
|
DRIVE_REMOVABLE
|
The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.
|
DRIVE_FIXED
|
The drive has fixed media; for example, a hard drive or flash drive.
|
或者采用一种稍微复杂一点的方法,使用我们第一节
http://cutebunny.blog.51cto.com/301216/624027
中介绍的
GetDriveGeometry()
函数,其输出参数
DISK_GEOMETRY *pdg
中的
MediaType
字段代表设备类型。
typedef enum _MEDIA_TYPE
{
…
RemovableMedia
FixedMedia
…
}MEDIA_TYPE;
这两个方法看似能方便快捷的解决我们的需求,但事实上当你使用
GetDriveType()
去获取一块移动硬盘的类型时,程序会坑爹的告诉你这块移动硬盘的类型是
DRIVE_FIXED
,根本无法与本地磁盘区分开来。
GetDriveGeometry()
函数的结果也是如此。
事实上,上述方法只对小容量的U
盘有效,会返回给你
DRIVE_REMOVABLE
的结果;而对移动硬盘甚至是一块稍大容量的U
盘(比如我有一块格式化为FAT32
格式的4G U
盘),就无能为力了。
所以,我们必须采用别的思路了,这里我介绍一种通过查看总线类型来区分本地磁盘和USB
磁盘的方法。当然,其基础还是我们那万能的
DeviceIoControl
,不过这次的控制码为
IOCTL_STORAGE_QUERY_PROPERTY
。同时对应的输入参数为
STORAGE_PROPERTY_QUERY
结构,输出参数为
STORAGE_DEVICE_DESCRIPTOR
结构体。
typedef struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId;
STORAGE_QUERY_TYPE QueryType;
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
STORAGE_PROPERTY_ID PropertyId;
STORAGE_QUERY_TYPE QueryType;
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
调用时需设置输入参数中的字段
PropertyId = StorageDeviceProperty;
QueryType = PropertyStandardQuery;
以表明我们要查询一个device descriptor
,也就是说,只有指定这种类型,输出参数才会得到
STORAGE_DEVICE_DESCRIPTOR
类型数据。
typedef struct _STORAGE_DEVICE_DESCRIPTOR {
ULONG Version;
ULONG Size;
UCHAR DeviceType;
UCHAR DeviceTypeModifier;
BOOLEAN RemovableMedia;
BOOLEAN CommandQueueing;
ULONG VendorIdOffset;
ULONG ProductIdOffset;
ULONG ProductRevisionOffset;
ULONG SerialNumberOffset;
STORAGE_BUS_TYPE BusType;
ULONG RawPropertiesLength;
UCHAR RawDeviceProperties[1];
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
ULONG Version;
ULONG Size;
UCHAR DeviceType;
UCHAR DeviceTypeModifier;
BOOLEAN RemovableMedia;
BOOLEAN CommandQueueing;
ULONG VendorIdOffset;
ULONG ProductIdOffset;
ULONG ProductRevisionOffset;
ULONG SerialNumberOffset;
STORAGE_BUS_TYPE BusType;
ULONG RawPropertiesLength;
UCHAR RawDeviceProperties[1];
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
其中,
BusType
定义为
typedef enum _STORAGE_BUS_TYPE {
BusTypeUnknown = 0x00,
BusTypeScsi,
BusTypeAtapi,
BusTypeAta,
BusType1394,
BusTypeSsa,
BusTypeFibre,
BusTypeUsb,
BusTypeRAID,
BusTypeiScsi,
BusTypeSas,
BusTypeSata,
BusTypeSd,
BusTypeMmc,
BusTypeMax,
BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
BusTypeUnknown = 0x00,
BusTypeScsi,
BusTypeAtapi,
BusTypeAta,
BusType1394,
BusTypeSsa,
BusTypeFibre,
BusTypeUsb,
BusTypeRAID,
BusTypeiScsi,
BusTypeSas,
BusTypeSata,
BusTypeSd,
BusTypeMmc,
BusTypeMax,
BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
明白了吧,如果总线类型为
BusTypeUsb
,就是找到了我们的USB
移动硬盘了。
但此时还需要解决一个问题,
STORAGE_DEVICE_DESCRIPTOR
可以理解为一个变长缓冲区,最后一个字段
RawDeviceProperties[1]
是可以动态扩展的(windows API
经常有这种情况)
,那么函数
DeviceIoControl()
中的参数
nOutBufferSize
应该填多少呢?这时我们需要借助另一个数据结构
STORAGE_DESCRIPTOR_HEADER
,在我们不知道device descriptor
实际需要多大的缓冲区时,可以先把
STORAGE_DESCRIPTOR_HEADER
作为输出参数以获得device descriptor
的缓冲区大小,其大小被存入header
的
size
字段中。
typedef struct _STORAGE_DESCRIPTOR_HEADER {
ULONG Version;
ULONG Size;
} STORAGE_DESCRIPTOR_HEADER, *PSTORAGE_DESCRIPTOR_HEADER;
ULONG Version;
ULONG Size;
} STORAGE_DESCRIPTOR_HEADER, *PSTORAGE_DESCRIPTOR_HEADER;
以下是具体代码
/******************************************************************************
* Function: get the bus type of an disk
* input: drive name (c:)
* output: bus type
* return: Succeed, 0
*
Fail, -1
******************************************************************************/
DWORD GetDriveTypeByBus(const CHAR *drive, WORD *type)
{
HANDLE hDevice; // handle to the drive to be examined
BOOL result; // results flag
DWORD readed; // discard results
STORAGE_DESCRIPTOR_HEADER *pDevDescHeader;
STORAGE_DEVICE_DESCRIPTOR *pDevDesc;
DWORD devDescLength;
STORAGE_PROPERTY_QUERY query;
hDevice = CreateFile(
drive, // drive to open
GENERIC_READ | GENERIC_WRITE, // access to the drive
FILE_SHARE_READ | FILE_SHARE_WRITE, //share mode
NULL, // default security attributes
OPEN_EXISTING, // disposition
0, // file attributes
NULL // do not copy file attribute
);
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
return DWORD(-1);
}
query.PropertyId = StorageDeviceProperty;
query.QueryType = PropertyStandardQuery;
pDevDescHeader = (STORAGE_DESCRIPTOR_HEADER *)malloc(sizeof(STORAGE_DESCRIPTOR_HEADER));
if (NULL == pDevDescHeader)
{
return (DWORD)-1;
}
result = DeviceIoControl(
hDevice, // device to be queried
IOCTL_STORAGE_QUERY_PROPERTY, // operation to perform
&query,
sizeof query, // no input buffer
pDevDescHeader,
sizeof(STORAGE_DESCRIPTOR_HEADER), // output buffer
&readed, // # bytes returned
NULL); // synchronous I/O
if (!result) //fail
{
fprintf(stderr, "IOCTL_STORAGE_QUERY_PROPERTY Error: %ld\n", GetLastError());
free(pDevDescHeader);
(void)CloseHandle(hDevice);
return DWORD(-1);
}
devDescLength = pDevDescHeader->Size;
pDevDesc = (STORAGE_DEVICE_DESCRIPTOR *)malloc(devDescLength);
if (NULL == pDevDesc)
{
free(pDevDescHeader);
return (DWORD)-1;
}
result = DeviceIoControl(
hDevice, // device to be queried
IOCTL_STORAGE_QUERY_PROPERTY, // operation to perform
&query,
sizeof query, // no input buffer
pDevDesc,
devDescLength, // output buffer
&readed, // # bytes returned
NULL); // synchronous I/O
if (!result) //fail
{
fprintf(stderr, "IOCTL_STORAGE_QUERY_PROPERTY Error: %ld\n", GetLastError());
free(pDevDescHeader);
free(pDevDesc);
(void)CloseHandle(hDevice);
return DWORD(-1);
}
//printf("%d\n", pDevDesc->BusType);
*type = (WORD)pDevDesc->BusType;
free(pDevDescHeader);
free(pDevDesc);
(void)CloseHandle(hDevice);
return 0;
}
代码说明:
1.
调用
CreateFile
打开并获得设备句柄。
2.
在输入参数
STORAGE_PROPERTY_QUERY query
中指定查询类型。
3.
以
STORAGE_DESCRIPTOR_HEADER *pDevDescHeader
为输出参数,调用操作码为
IOCTL_STORAGE_QUERY_PROPERTY
的
DeviceIoControl
函数获得输出缓冲区大小。
4.
按3
中获得的缓冲区大小为
STORAGE_DEVICE_DESCRIPTOR *pDevDesc
分配空间,以
pDevDesc
为输出参数,调用操作码为
IOCTL_STORAGE_QUERY_PROPERTY
的
DeviceIoControl
函数获得device descriptor
。
5.
从device descriptor
中获得
BusType
。