如何判断两个可移动磁盘卷是否在同一个USB HUB上?
2012-11-12 09:27
876 查看
这是一个近期的项目需求功能点,参考相关资料,现在将研究所得的一些想法的实现分享一下。判断多个可移动磁盘卷是否在同一个USB HUB上,我们知道,每一个可移动磁盘卷都有一些唯一的标识,起先的想法是枚举USB可移动卷设备,总觉得设备信息里会有一些关于USB HUB的信息,不过后来没有找到就放弃这种想法了(如果我忽略了有牛们知道的话讨论下)。现在是从USB PORT入手,每个USB HUB上都有一个 NNumberOfPort来指示这个HUB有几个USB PORT,我们需要遍历这个。我们需要获取的是可移动磁盘的VID,PID和SerialNumber这三个标识。在USB
PORT上挂接的设备中是可以找到这三个信息的,所以我们匹配这些信息就能找到指定的可移动磁盘对应的是哪一个USB HUB,从而达到标题所指的功能。
USB HUB设备是挂在HCD上的(控制总线),我们得先枚举这个。
一、枚举HCD( Host Controller)
这个很简单,HCD是这样的命名(\\.\HCD0,\\.\HCD1)
获取到HCD的设备句柄之后,我们需要获取它下面的首个HUB的设备。
二、根据HCD的设备句柄获取HUB设备
用CreateFile找开时需要名字,首先获取该hub的名字,向hcd设备发IOCTL_USB_GET_ROOT_HUB_NAME就可以了。
有了HUB的句柄,那关于这个HUB的信息,比如我们关心的nNumberOfPorts也能获取到了,向这个设备发IOCTL_USB_GET_NODE_INFORMATION请求。
接下去可以去遍历这些端口了!
三、遍历HUB上的usb port
获取一个指定HUB上的端口信息,向这个HUB设备发送IOCTL_USB_GET_NODE_CONNECTION_INFORMATION/EX,因为SerialNumber这个唯一标识是字符串值,而获取到的端口连接信息里只会有iSerialNumber这个指示offset的字段,所以我们还需要发送一个IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION去获取这个UB的字符串描述,然后根据iSerialNumber 这个offset来偏移到SerialNumber,而VID和PID则以USHORT类型直接在设备描述里出现。
信息结构如下:
USB的设备描述结构:
现在有了这三个唯一的标识,只要跟目标可移动磁盘匹配就可以了,一般我们实际有都不会指定一个可移动磁盘设备,而是指定一个盘符,接下来我们讨论如何通过盘符获取上面这三个唯一标识(SerialNumber,VID,PID)。
四、通过盘符获取磁盘设备的唯一标识
可移动磁盘,包括U盘,也包括移动硬盘,需要注意的分区。两个不同的分区,但是他们是处于同一个设备,所以Disk Number是一样的,我这里用这个来处理带有分区的可移动磁盘。
很简单,只要向磁盘卷设备发送IOCTL_STORAGE_GET_DEVICE_NUMBER就可以了,获取到的信息结构如下:
获取到disk number之后我们可以用CM_Get_Device_ID函数获取这些信息,但是这个信息的第一个参数是DEVINST类型,可以通过上面获取到的disk number,用SetupDi函数枚举出来。
(网上现成代码如下)
通过CM_Get_Device_ID能获取到的只是SerialNumber,在获取到的字符串的最后一节(以‘\’)隔开
通过下面代码提取SerialNumber:
获取vid和pid也是通过setupdi函数枚举出来,在获取到DevicePath之后通过下面代码获取:
注:关于SerialNumber的获取,DevicePath里也有SerialNumber,也可以通过上面的方法提取出来,而不用过CM_Get_Device_ID函数
这样就获取到指定盘符的三个唯一标识,去跟指定usb hub上挂接的设备进行比较,就可以知道两个卷是不是挂接在同一个usb hub上了。
来自:http://bbs.pediy.com/archive/index.php?t-130146.html
PORT上挂接的设备中是可以找到这三个信息的,所以我们匹配这些信息就能找到指定的可移动磁盘对应的是哪一个USB HUB,从而达到标题所指的功能。
USB HUB设备是挂在HCD上的(控制总线),我们得先枚举这个。
一、枚举HCD( Host Controller)
这个很简单,HCD是这样的命名(\\.\HCD0,\\.\HCD1)
for( i = 0; ; i++ ) { wsprintf( szHcName,_T(\\\\.\\HCD%d),i ); handleHcd = CreateFile( szHcName…. ); if( INVALID_HANDLE_VALUE != handleHcd ) { //... CloseHandle( handleHcd ); } else { break; } } ...
获取到HCD的设备句柄之后,我们需要获取它下面的首个HUB的设备。
二、根据HCD的设备句柄获取HUB设备
用CreateFile找开时需要名字,首先获取该hub的名字,向hcd设备发IOCTL_USB_GET_ROOT_HUB_NAME就可以了。
typedef struct _USB_ROOT_HUB_NAME { ULONG ActualLength; //指示长度 WCHAR RootHubName[1]; //名字的指针 } USB_ROOT_HUB_NAME, *PUSB_ROOT_HUB_NAME; USB_ROOT_HUB_NAME hubNameTmp; PUSB_ROOT_HUB_NAME pHubName; //获取名字长度 DeviceIoControl( hHCDHandle,IOCTL_USB_GET_ROOT_HUB_NAME, &hubNameTmp,sizeof(USB_ROOT_HUB_NAME), &hubNameTmp,sizeof(USB_ROOT_HUB_NAME),&dwBytes,NULL ); dwBytes = hubNameTmp.ActualLength; //在结构的这个字段会返回所需的缓冲区长度 //获取名字 ... pHubName = (PUSB_ROOT_HUB_NAME)malloc( dwBytes ); if( pHubName ) { pHubName->ActualLength = dwBytes; if( DeviceIoControl( hHCDHandle, IOCTL_USB_GET_ROOT_HUB_NAME, NULL,0, pHubName,dwBytes, &dwBytes,NULL ) ) { //获取成功,申请返回字符串内存 lpszHCDName = (TCHAR*)malloc( dwBytes ); if( lpszHCDName ) { //获取到的是UNICODE字符串 WCharToMByte( pHubName->RootHubName,lpszHCDName,dwBytes ); //接下来可以根据这个名字打开这个句柄了 wsprintf( szName,_T("\\\\.\\%s"),lpszHubName ); hHubHandle = CreateFile( szName,GENERIC_WRITE, FILE_SHARE_WRITE,NULL,OPEN_EXISTING,NULL,NULL ); if( INVALID_HANDLE_VALUE != hHubHandle ) { //成功获取到hub设备句柄 CloseHandle( hHubHandle ); } } } //释放内存 free( pHubName ); } ...
有了HUB的句柄,那关于这个HUB的信息,比如我们关心的nNumberOfPorts也能获取到了,向这个设备发IOCTL_USB_GET_NODE_INFORMATION请求。
... USB_NODE_INFORMATION NodeInfo; if( DeviceIoControl( hHubHandle,IOCTL_USB_GET_NODE_INFORMATION,NULL,0, &NodeInfo,sizeof(USB_NODE_INFORMATION),&dwBytes,NULL ) { //这里就能获取到这个HUB上的USB端口数了,在hub的设备描述符中 NumberOfPorts = NodeInfo.u.HubInformation.HubDescriptor.bNumberOfPorts; } ...
接下去可以去遍历这些端口了!
三、遍历HUB上的usb port
获取一个指定HUB上的端口信息,向这个HUB设备发送IOCTL_USB_GET_NODE_CONNECTION_INFORMATION/EX,因为SerialNumber这个唯一标识是字符串值,而获取到的端口连接信息里只会有iSerialNumber这个指示offset的字段,所以我们还需要发送一个IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION去获取这个UB的字符串描述,然后根据iSerialNumber 这个offset来偏移到SerialNumber,而VID和PID则以USHORT类型直接在设备描述里出现。
信息结构如下:
#pragma pack(1) typedef struct _USB_NODE_CONNECTION_INFORMATION_EX { ULONG ConnectionIndex; //这个值是需要输入的,指定查询哪一个端口,从数字1开始 USB_DEVICE_DESCRIPTOR DeviceDescriptor; //如果有USB连接的话这里是连接的USB设备的描述符, //也是我们用来比较两个可移动磁盘卷是不是相同的标识 UCHAR CurrentConfigurationValue; UCHAR Speed; BOOLEAN DeviceIsHub; //表示这个设备是不是一个HUB,也就是说如果是一个HUB的话我们需要递归下去 USHORT DeviceAddress; ULONG NumberOfOpenPipes; USB_CONNECTION_STATUS ConnectionStatus; //这个端口的连接状态 USB_PIPE_INFO PipeList[0]; } USB_NODE_CONNECTION_INFORMATION_EX, *PUSB_NODE_CONNECTION_INFORMATION_EX; #pragma pack()
USB的设备描述结构:
#pragma pack(1) typedef struct _USB_DEVICE_DESCRIPTOR { UCHAR bLength ; UCHAR bDescriptorType ; USHORT bcdUSB ; UCHAR bDeviceClass ; UCHAR bDeviceSubClass ; UCHAR bDeviceProtocol ; UCHAR bMaxPacketSize0; USHORT idVendor ; //VID USHORT idProduct ; //PID USHORT bcdDevice ; UCHAR iManufacturer ; UCHAR iProduct ; UCHAR iSerialNumber ; //这个字段是指示在连接字符串信息中SerialNumber的offset UCHAR bNumConfigurations ; } USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR ; #pragma pack()
... PUSB_NODE_CONNECTION_INFORMATION_EX pNodeConnInfoEx; for( int I = 0; I <= NumPorts; I ++ ) { dwBytes = sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + sizeof(USB_PIPE_INFO) * 32; pNodeConnInfoEx = (PUSB_NODE_CONNECTION_INFORMATION_EX)malloc( dwBytes ); if( pNodeConnInfoEx ) { memset( pNodeConnInfoEx,0,dwBytes ); //这里是输入参数,指定要查高询哪一个端口的连接信息,从1开始 pNodeConnInfoEx->ConnectionIndex = I; DeviceIoControl( hHubHandle,IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, pNodeConnInfoEx,dwBytes,pNodeConnInfoEx,dwBytes,&dwBytes,NULL ); //这个端口有挂接设备 if( pNodeConnInfo->ConnectionStatus == DeviceConnected ) { //这个设备有这三个主要信息 if( pNodeConnInfo->DeviceDescriptor.idVendor && pNodeConnInfo->DeviceDescriptor.idProduct && pNodeConnInfo->DeviceDescriptor.iSerialNumber ) { //获取字符串描述(IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION) PUSB_DESCRIPTOR_REQUEST stringDescReq; //这里输入参数结构,需要填充 PUSB_STRING_DESCRIPTOR stringDesc; PSTRING_DESCRIPTOR_NODE stringDescNode; nBytes = sizeof(stringDescReqBuf); stringDescReq = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf; stringDesc = (PUSB_STRING_DESCRIPTOR)(stringDescReq+1); //填充输入查询信息参数 memset(stringDescReq, 0, nBytes); //连接的端口索引 stringDescReq->ConnectionIndex = ConnectionIndex; //这个wValume是一个WORD值,高8位指标查询类型,这里要查询字符串描述, //所以是USB_STRING_DESCRIPTOR,低8位指定查询的描述符索引, //比如要查询SerialNumber,这个值就是上面获取到的iSerialNumber stringDescReq->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) | DescriptorIndex; //语言ID,置为0即可 stringDescReq->SetupPacket.wIndex = LanguageID; //指示整个输出缓冲的大小,用总大小nBytes减去查询结构大小, stringDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST)); if( DeviceIoControl(hHubDevice, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, stringDescReq,nBytes,stringDescReq,nBytes,&nBytesReturned,NULL); { //获取到的是UNICODE字符串 TCHAR* pSerialNumberString = (TCHAR*)malloc( stringDesc->bLength / sizeof(WCHAR) + 1 ); //获取iSErialNumber在字符串描述中的偏移, //stringDesc->bString + descChars就是SerialNumber的UNICODE字符串了 int descChars = ( (int)stringDesc->bLength - offsetof(USB_STRING_DESCRIPTOR,bString)) / sizeof(WCHAR); if(pSerialNumberString) { //转为ansi字符串 stringDesc->bString[descChars] = 0; WCharToMByte( stringDesc->bString, pSerialNumberString, stringDesc->bLength / sizeof(WCHAR) + 1 ); //现在有了这三个信息就可以匹配可移动磁盘了 // pNodeConnInfo->DeviceDescriptor.idVendor // pNodeConnInfo->DeviceDescriptor.idProduct // pSerialNumberString …… } } } } ...
现在有了这三个唯一的标识,只要跟目标可移动磁盘匹配就可以了,一般我们实际有都不会指定一个可移动磁盘设备,而是指定一个盘符,接下来我们讨论如何通过盘符获取上面这三个唯一标识(SerialNumber,VID,PID)。
四、通过盘符获取磁盘设备的唯一标识
可移动磁盘,包括U盘,也包括移动硬盘,需要注意的分区。两个不同的分区,但是他们是处于同一个设备,所以Disk Number是一样的,我这里用这个来处理带有分区的可移动磁盘。
很简单,只要向磁盘卷设备发送IOCTL_STORAGE_GET_DEVICE_NUMBER就可以了,获取到的信息结构如下:
typedef struct _STORAGE_DEVICE_NUMBER { DEVICE_TYPE DeviceType; //这里是FILE_DEVICE_DISK DWORD DeviceNumber; //这里就是我们需要的DiskNumber DWORD PartitionNumber; } STORAGE_DEVICE_NUMBER, *PSTORAGE_DEVICE_NUMBER; ... wsprintf( szName,_T(\\\\.\\%c:),cVolume ); HANDLE hDevice = CreateDevice( szName,…. ); ... DeviceIoControl( hDevice,IOCTL_STORAGE_GET_DEVICE_NUMBER,NULL,0, &sdNumber,sizeof(STORAGE_DEVICE_NUMBER),&dwBytes,NULL ); ... //sdNumber.DeviceNumber就是DISK NUMBER
获取到disk number之后我们可以用CM_Get_Device_ID函数获取这些信息,但是这个信息的第一个参数是DEVINST类型,可以通过上面获取到的disk number,用SetupDi函数枚举出来。
(网上现成代码如下)
DEVINST GetDrivesDevInstByDiskNumber( long DiskNumber ) { DEVINST devInstResult = 0; GUID* guid = (GUID*)&GUID_DEVINTERFACE_DISK; //GUID* guid = (GUID*)&UsbClassGuid; HDEVINFO hDevInfo = SetupDiGetClassDevs( guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfo == INVALID_HANDLE_VALUE) { return 0; } DWORD dwIndex = 0; SP_DEVICE_INTERFACE_DATA devInterfaceData = {0}; devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); BOOL bRet = FALSE; BYTE Buf[1024]; PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf; SP_DEVICE_INTERFACE_DATA spdid; SP_DEVINFO_DATA spdd; DWORD dwSize; spdid.cbSize = sizeof(spdid); while ( true ) { bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &devInterfaceData); if (!bRet) { break; } SetupDiEnumInterfaceDevice(hDevInfo, NULL, guid, dwIndex, &spdid); dwSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); if ( dwSize!=0 && dwSize<=sizeof(Buf) ) { pspdidd->cbSize = sizeof(*pspdidd); ZeroMemory((PVOID)&spdd, sizeof(spdd)); spdd.cbSize = sizeof(spdd); long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd); if ( res ) { HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); if ( hDrive != INVALID_HANDLE_VALUE ) { STORAGE_DEVICE_NUMBER sdn; DWORD dwBytesReturned = 0; res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL); if ( res ) { if ( DiskNumber == (long)sdn.DeviceNumber ) { CloseHandle(hDrive); SetupDiDestroyDeviceInfoList(hDevInfo); devInstResult = spdd.DevInst; break; } } CloseHandle(hDrive); } } } dwIndex++; } return devInstResult; }
通过CM_Get_Device_ID能获取到的只是SerialNumber,在获取到的字符串的最后一节(以‘\’)隔开
通过下面代码提取SerialNumber:
if( CR_SUCCESS == CM_Get_Device_ID( devInst,szBuffer,200,0 ) ) { p = _tcsrchr( szBuffer,TEXT('\\') ); if( p ) { _tcscpy( pDid->SerialNumber,p+1 ); p = _tcschr( pDid->SerialNumber,TEXT('&') ); if( p ) { p[0] = 0; } } }
获取vid和pid也是通过setupdi函数枚举出来,在获取到DevicePath之后通过下面代码获取:
sscanf( pDetail->DevicePath, _T("\\\\?\\usb#vid_%04X&pid_%04X"),&Vid,&Pid );
注:关于SerialNumber的获取,DevicePath里也有SerialNumber,也可以通过上面的方法提取出来,而不用过CM_Get_Device_ID函数
这样就获取到指定盘符的三个唯一标识,去跟指定usb hub上挂接的设备进行比较,就可以知道两个卷是不是挂接在同一个usb hub上了。
来自:http://bbs.pediy.com/archive/index.php?t-130146.html
相关文章推荐
- 如何判断两个可移动磁盘卷是否在同一个USB HUB上?
- 一个算法:关于如何判断两个时间是否在允许误差内相等
- java怎么判断两个Set 里的对象的值是否相同【两个set中的值是否相等】、java treeset和hashset如何判断元素是否相同【即对象是否完全相同;利用一个set去除重复元素】
- 【OC学习-15】如何判断两个对象是否属于同一个类?如何判断一个对象是否属于某一个类?
- [置顶] 如何判断两个IP大小关系及是否在同一个网段中
- 同表两个字段二选一查询mysql中如何判断某一个字段是否存在某一个值
- 如何判断两个IP大小关系及是否在同一个网段中
- 如何判断一个程序是否会有线程安全问题?
- VBS中如何判断一个引用对象是否存在?
- 编写一个函数isMerge,判断一个字符串str是否可以由其他两个字符串part1和part2“组合”而成
- 如何判断一个单向链表是否有环路?
- 微软面试(or电面)试题——如何判断一个单链表是否存在回路
- jquery和js如何判断一个对象是否存在
- 如何用最简单最快的方法判断一个BMP图片是否为黑白图片?我现在是全图片scanline 然后再加以分析,太慢了,有没有直接的函数可以做到呢?
- IE下如何判断一个 div是否隐藏
- 如何判断一个js对象是否一个DOM对象
- 如何判断一个数组是否按顺序排好了
- 如何判断一个字符串是否全由数字组成
- JAVA中如何判断一个数组是否为空