区分本地磁盘和外接磁盘的三种方法


最近在做一个检测存储器稳定性的一个项目,要检测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,所以它任然存在局限性。

总结了以上三种方法,希望可以给正在处于困扰中的你一点思路,让我们共同探讨一下此项命题。

这是我发表的第一篇博文 ,后两种方法都是参考网上的资料, 这篇中就有很多参考,大家可以看看。




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值