最近在做一个检测存储器稳定性的一个项目,要检测SD卡或者SSD的稳定性,过程中遇到了一些问题,所以在这里总结记录下来,等到后期回顾完善,也和大家交流一下。
既然要检测存储器(SD、SSD固态硬盘或者机械硬盘等),就需要先弄清楚电脑都连接了哪些存储设备,有几个,都是什么类型的,以及其各自的属性。我在网络中搜索了很多内容,目前基本解决了怎么区分本地硬盘和外接硬盘。但是如何从根本上区分刚买的电脑中的磁盘是机械硬盘还是SSD固态硬盘,还望大家不吝赐教。
不说废话了,下面总结一下我所找到的三种方法。
方法一 利用DeviceIoCtrl()的 DISK_GEOMETRY 参数
要想和底层硬件设备打交道
DeviceIoControl()这个函数肯定少不了。它的参数很多,可能有很多方式可以查询存储器的属性,我使用了OCTL_DISK_GET_DRIVE_GEOMETRY参数来获取物理磁盘的属性。(这里说明一下,电脑中的每个卷区是一个逻辑磁盘,它们是由物理磁盘分区产生的)
//获取物理磁盘信息
BOOL GetDriveGeometry(LPWSTR driverPath, DISK_GEOMETRY *geo);
{
HANDLE hDevice = INVALID_HANDLE_VALUE; // handle to the drive to be examined
BOOL bResult = FALSE; // results flag
DWORD junk = 0; // discard results
hDevice = CreateFileW(driverPath, // drive to open
GENERIC_READ, // no access to the drive
FILE_SHARE_READ | // share mode
FILE_SHARE_WRITE,
NULL, // default security attributes
OPEN_EXISTING, // disposition
0, // file attributes
NULL); // do not copy file attributes
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
return (FALSE);
}
bResult = DeviceIoControl(hDevice, // device to be queried
IOCTL_DISK_GET_DRIVE_GEOMETRY, // operation to perform
NULL, 0, // no input buffer
geo, sizeof(*geo), // output buffer
&junk, // # bytes returned
(LPOVERLAPPED) NULL); // synchronous I/O
DWORD i = GetLastError(); //获取产生错误信息原因
CloseHandle(hDevice);
return (bResult);
}
geo参数中就包含了物理磁盘的类型,扇区大小等类型。
typedef struct _DISK_GEOMETRY {
LARGE_INTEGER Cylinders;
MEDIA_TYPE MediaType;
DWORD TracksPerCylinder;
DWORD SectorsPerTrack;
DWORD BytesPerSector;
} DISK_GEOMETRY;
这里得到的
MediaType只能区分硬盘和优盘,而不能区分硬盘的类型(是否外接)。这时可以通过获取系统所在盘来区分了。利用GetSystemDirectory()函数可以获取系统所在盘的路径,再然再找出系统盘的物理磁盘号后就可以区分哪一个硬盘是电脑自带的,其他都是外接的。
BOOL GetDeviceInfomation(LPSTR devicePath,STORAGE_DEVICE_NUMBER * sdn) //通过系统盘路径获得物理磁盘号
{
HANDLE hDevice = INVALID_HANDLE_VALUE; // handle to the drive to be examined
BOOL bResult = FALSE; // results flag
DWORD bufSize = 0; // discard results
//sprintf_s(devName,"\\\\.\\%c:",diskLetter);
hDevice = CreateFile(devicePath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE ,
NULL,
OPEN_EXISTING,
0,//或者FILE_ATTRIBUTE_NORMAL
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
DWORD lasterror = GetLastError();
return FALSE;
}
else
{
bResult = DeviceIoControl(hDevice,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL,
0,
sdn,
sizeof(STORAGE_DEVICE_NUMBER),
&bufSize,
NULL);
if(bResult)
{
CloseHandle(hDevice);
return TRUE;
}
else
{
DWORD error = GetLastError();
return FALSE;
}
}
}
通过sdn.DeviceNumber就可得到物理磁盘号,然后就可以区分外接磁盘了。
方法二 通过总线类型判断 还是利用DeviceIoControl()
此种方法是利用GetLogicalDriveStrings()获取逻辑磁盘,然后用GetDriveType()查出逻辑磁盘类型后再判断逻辑磁盘的总线类型来辨别,一般系统磁盘的总线类型是BusTypeAta,而外接磁盘的总线类型就是BusTypeUsb.
CHAR szLogicalDriveStrings[BUFSIZE];
PCHAR szDrive;
ZeroMemory(szLogicalDriveStrings,BUFSIZE);
// 获取逻辑驱动器卷标名
GetLogicalDriveStrings(BUFSIZE - 1,szLogicalDriveStrings);
szDrive = (PCHAR)szLogicalDriveStrings;
// 循环处理每个卷
int drive;
for (drive = 0;drive < 10 && *szDrive!='\x00';drive++)
{
if(DRIVE_FIXED == ::GetDriveType(szDrive))
{
char t = szDrive[0];
char diskPath[MAX_PATH];
memset(diskPath,'\0',MAX_PATH);
sprintf_s(diskPath,"\\\\.\\%c:\0",t ); //格式化字符串
if (isUsbDriver(diskPath))
{
volumeInfo.m_uVolumeType = DRIVE_REMOVABLE_FIXED;//自己定义一个类型
}
}
}
//此函数只有在SSD硬盘从USB接口接入时有效
BOOL CLogicalDriver::isUsbDriver(LPCTSTR diskPath)
/*Routine Description:
判断某分区是否是USB设备。
Arguments:
diskPath - 输入参数,驱动器路径,例:"\\.\c:"。
Return Value:
是USB设备则返回TRUE,否则返回FALSE,
查询失败也返回FALSE。
--*/
{
//TCHAR tcsDrvName[] = TEXT("\\\\.\\PhysicalDrive0\0");
//tcsDrvName[4] = tchDrvName;
HANDLE hDevice = CreateFile( diskPath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( hDevice == INVALID_HANDLE_VALUE ) {
return FALSE;
}
STORAGE_PROPERTY_QUERY StoragePropertyQuery;
//表明查询设备描述符Indicates that the caller is querying for the device descriptor
StoragePropertyQuery.PropertyId = StorageDeviceProperty;
//Instructs the port driver to report a device descriptor,
//an adapter descriptor or a unique hardware device ID (DUID).
StoragePropertyQuery.QueryType = PropertyStandardQuery;
BYTE buff[1024] = {0};
PSTORAGE_DEVICE_DESCRIPTOR pDevDesc = (PSTORAGE_DEVICE_DESCRIPTOR)buff;
pDevDesc->Size = sizeof(buff);
DWORD dwOutLen;
BOOL res = DeviceIoControl( hDevice,
IOCTL_STORAGE_QUERY_PROPERTY,
&StoragePropertyQuery,
sizeof(STORAGE_PROPERTY_QUERY),
pDevDesc,
pDevDesc->Size,
&dwOutLen,
NULL);
CloseHandle(hDevice);
if (res) {
// 返回结果
return (pDevDesc->BusType == BusTypeUsb);//BusType是总线类型
} else {
// 查询失败
return FALSE;
}
}
但是我的电脑有一个SATA接口,所以这样任然不足以区分我外接的磁盘和电脑原来的磁盘。然后我有用了另外一种方法就是查询设备注册表,这种方法最终也没有带来很好的效果,但是和第二种方法有相似之处,还是贴下来记录一下吧。
方法三 读取设备注册表
这个方法还有点问题,有兴趣的话可以改进一下。
BOOL CLogicalDriver::isIDE(CString DriveName)
{
本段程序的前提是DriveName是已经过GetDriveType的判断是本地磁盘,
//否则报错,
//111111111111111111111111111111111111111
///获得某分区(目的地址)的信息/
HANDLE hDeviceDest = NULL;
DWORD nBytesRead = 0;//预设为0,当缓冲区的长度不够时,该值为所需的缓冲区的长度
DWORD nBufferSize = sizeof(PARTITION_INFORMATION);
PPARTITION_INFORMATION lpPartInfo = (PPARTITION_INFORMATION)malloc(nBufferSize);
if(lpPartInfo == NULL)
{
//MessageBox("缓冲区分配出错!","失败!",MB_OK);
return FALSE;
}
memset(lpPartInfo, 0, nBufferSize);//将缓冲区lpPartInfo的内容设为nDiskBufferSize个NULL
//CString DriveName="J:";//为判断提供接口
DriveName="\\\\.\\"+DriveName;
hDeviceDest = CreateFile(LPCTSTR(DriveName),//注意一定要是\\\\.\\的形式,CreateFile的要求 ""\\??\\Volume{e9233817-90be-11d6-88b7-00e04c3de005}
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
0, NULL);
if(hDeviceDest == NULL)
{
//MessageBox("CreateFile出错!","失败!",MB_OK);
return FALSE;
}
/获得该分区信息/
BOOL ret1=DeviceIoControl(
hDeviceDest,
IOCTL_DISK_GET_PARTITION_INFO,
NULL,
0,
(LPVOID) lpPartInfo,
(DWORD) nBufferSize,
(LPDWORD) &nBytesRead,
NULL//指向一个异步的结构体
);
if(!ret1)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL);
//MessageBox( (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
LocalFree( lpMsgBuf );
//MessageBox("DeviceIoControl出错!","失败!",MB_OK);
return FALSE;
}
///导出该分区信息///
LARGE_INTEGER StartingOffset=lpPartInfo->StartingOffset;
LONGLONG QuadPart=StartingOffset.QuadPart;//取上面的值之一情形,支持64位整型
LARGE_INTEGER PartitionLength=lpPartInfo->PartitionLength;
LONGLONG QuadPart1=PartitionLength.QuadPart;//取上面的值之一情形,支持64位整型
DWORD HiddenSectors=lpPartInfo->HiddenSectors;
DWORD PartitionNumber=lpPartInfo->PartitionNumber;
BYTE PartitionType=lpPartInfo->PartitionType;
BOOLEAN BootIndicator=lpPartInfo->BootIndicator;
BOOLEAN RecognizedPartition=lpPartInfo->RecognizedPartition;
BOOLEAN RewritePartition=lpPartInfo->RewritePartition;
free(lpPartInfo);
CloseHandle(hDeviceDest);
/查询注册表中COUNT(Disk)的值//
UINT IDESeqNum;//IDE的序号
BOOL FindIDE = FALSE;
HKEY hKEY;
RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\Disk\\Enum",0, KEY_READ, &hKEY);
///接收DWORD型/
DWORD Type;//仅仅用于接收数据类型
DWORD dwValue;
DWORD dwBufLen = sizeof(DWORD);
long ret2 = ::RegQueryValueEx(hKEY, _T("Count"), NULL, &Type, (BYTE*)&dwValue, &dwBufLen);
if(ret2 != ERROR_SUCCESS)
{
//MessageBox("找不到磁盘的个数","提示",MB_OK);
return FALSE;//失败
}
for (UINT k=0; k < dwValue; k++)
{
///接收字符型/
char str[256];
DWORD sl = 256;
CString nDisk;
nDisk.Format("%u",k);
RegQueryValueEx(hKEY, nDisk, NULL, NULL, (LPBYTE)str, &sl);//注意第三项必须设为NULL,否则接收到的字符数据出错
CString temp=str; //此处可以查看注册表中的数据,前面几位是代表存储设备的类型,IDE、RISD、USBSTOR等(具体什么类型不知道)???
if (temp.Left(3)=="IDE")
{
IDESeqNum=k;//IDE的序号
FindIDE = TRUE;
}
}
if(!FindIDE)
return FALSE; // IDESeqNum=0;
RegCloseKey(hKEY);
CString temp;
temp.Format("%u",IDESeqNum);
temp="\\\\.\\PHYSICALDRIVE"+temp;//为下一步检测作准备
//22222222222222222222222222222222222222222 /
HANDLE hDevice = NULL;
DWORD nDiskBytesRead = 0;//预设为0,当缓冲区的长度不够时,该值为所需的缓冲区的长度
DWORD nDiskBufferSize = sizeof(DRIVE_LAYOUT_INFORMATION) + sizeof(PARTITION_INFORMATION)*104;//26*4
PDRIVE_LAYOUT_INFORMATION lpDiskPartInfo = (PDRIVE_LAYOUT_INFORMATION)malloc(nDiskBufferSize);
if(lpDiskPartInfo == NULL)
{
//MessageBox("缓冲区分配出错!","失败!",MB_OK);
return FALSE;
}
memset(lpDiskPartInfo, 0, nDiskBufferSize);//将缓冲区lpDiskPartInfo的内容设为nDiskBufferSize个NULL
//获得所有分区的信息///
hDevice = CreateFile(LPCTSTR(temp),//注意一定要是\\\\.\\的形式,CreateFile的要求 ""\\??\\Volume{e9233817-90be-11d6-88b7-00e04c3de005}
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
0, NULL);
if(hDevice == NULL)
{
//MessageBox("CreateFile出错!","失败!",MB_OK);
return FALSE;
}
/获得某磁盘上的所有分区信息/
BOOL ret=DeviceIoControl(
hDevice,
IOCTL_DISK_GET_DRIVE_LAYOUT,
NULL,
0,
(LPVOID) lpDiskPartInfo,
(DWORD) nDiskBufferSize,
(LPDWORD) &nDiskBytesRead,
NULL
);
if (!ret)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL);
//MessageBox( (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
LocalFree( lpMsgBuf );
//MessageBox("DeviceIoControl出错!","失败!",MB_OK);
return FALSE;
}
//导出分区信息///
DWORD PartitionCount=lpDiskPartInfo->PartitionCount; //永远是实际的分区数的4倍,不能用的分区将会显示类型PARTITION_ENTRY_UNUSED,即分区类型为0
///依次获取导出某分区信息,并与目的驱动器进行比较///
for (UINT i=0; i<PartitionCount; i=i+4)//+4是因为只有下标为4的整数倍的值才是正确的引用
{
PARTITION_INFORMATION DiskPartInfo=lpDiskPartInfo->PartitionEntry[i];//0为C:,4为D:,8为e:,12为F
LARGE_INTEGER DiskStartingOffset = DiskPartInfo.StartingOffset;
LONGLONG DiskQuadPart = DiskStartingOffset.QuadPart; //取上面的值之一情形,支持64位整型
LARGE_INTEGER DiskPartitionLength = DiskPartInfo.PartitionLength;
LONGLONG DiskQuadPart1 = DiskPartitionLength.QuadPart; //取上面的值之一情形,支持64位整型
DWORD DiskHiddenSectors = DiskPartInfo.HiddenSectors;
DWORD DiskPartitionNumber = DiskPartInfo.PartitionNumber;
BYTE DiskPartitionType = DiskPartInfo.PartitionType;
BOOLEAN DiskBootIndicator = DiskPartInfo.BootIndicator;
BOOLEAN DiskRecognizedPartition = DiskPartInfo.RecognizedPartition;
BOOLEAN DiskRewritePartition = DiskPartInfo.RewritePartition;
if ((DiskQuadPart==QuadPart) && (DiskQuadPart1==QuadPart1)
&& (DiskHiddenSectors==HiddenSectors) && (DiskPartitionNumber==PartitionNumber)
&& (DiskPartitionType==PartitionType ) && (DiskBootIndicator==BootIndicator)
&& (DiskRecognizedPartition==RecognizedPartition) && (DiskRewritePartition==RewritePartition))
{
free(lpDiskPartInfo);
CloseHandle(hDevice);
::MessageBox(NULL,"属于本地驱动器!","提示",MB_OK);
return TRUE;
}
}
free(lpDiskPartInfo);
CloseHandle(hDevice);
::MessageBox(NULL,"非本地驱动器!","提示",MB_OK);//改为return IDCANCEL;
return FALSE;
}
这个函数我主要要说的就是下面的这段
/查询注册表中COUNT(Disk)的值//
UINT IDESeqNum;//IDE的序号
BOOL FindIDE = FALSE;
HKEY hKEY;
RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\Disk\\Enum",0, KEY_READ, &hKEY);
///接收DWORD型/
DWORD Type;//仅仅用于接收数据类型
DWORD dwValue;
DWORD dwBufLen = sizeof(DWORD);
long ret2 = ::RegQueryValueEx(hKEY, _T("Count"), NULL, &Type, (BYTE*)&dwValue, &dwBufLen);
if(ret2 != ERROR_SUCCESS)
{
//MessageBox("找不到磁盘的个数","提示",MB_OK);
return FALSE;//失败
}
for (UINT k=0; k < dwValue; k++)
{
///接收字符型/
char str[256];
DWORD sl = 256;
CString nDisk;
nDisk.Format("%u",k);
RegQueryValueEx(hKEY, nDisk, NULL, NULL, (LPBYTE)str, &sl);//注意第三项必须设为NULL,否则接收到的字符数据出错
CString temp=str; //此处可以查看注册表中的数据,前面几位是代表存储设备的类型,IDE、RISD、USBSTOR等(具体什么类型不知道)???
if (temp.Left(3)=="IDE")
{
IDESeqNum=k;//IDE的序号
FindIDE = TRUE;
}
}
if(!FindIDE)
return FALSE; // IDESeqNum=0;
RegCloseKey(hKEY);
通过断点调试发现temp的值获取的就是注册表中的信息,例如系统盘读出的信息如下 "IDE\DiskWDC_WD3200BPVT-08JJ5T0____01.01A01\5&185d3c4a&0&0.0.0" ,前几个字母代表了磁盘的类型,如果硬盘用USB口接入则为USBSTRO,如果用SATA接口接入也是IDE,所以它任然存在局限性。
总结了以上三种方法,希望可以给正在处于困扰中的你一点思路,让我们共同探讨一下此项命题。
这是我发表的第一篇博文 ,后两种方法都是参考网上的资料,
这篇中就有很多参考,大家可以看看。