usbview 是微软随wdk 一起发布的一个usb设备查看源码,能够对系统中全部的USB设备进行枚举,可以查看每个设备的详细信息。既使没有安装相应的驱动也可以。
如何编译请参考:
http://blog.csdn.net/chenyujing1234/article/details/7577320
这篇文章对于这个程序的大体逻辑已经分析的差不多了,因为本人工作需也对该程序进行了深入的研究,花费了很多的时间,这里把我的一些个人所得记录下来,以助他人。
知识在于共享嘛!
一、整体介绍
微软已经对该程序进行了更新,代码位于
http://code.msdn.microsoft.com/windowshardware/USBView-sample-application-e3241039/
但是这个代码只能在vs2012及以上中使用,且只支持window7以上操作系统。
本文所用的代码可以在winDDK 7600的安装目录下找到。 src/usb下面。
程序用C写成,且年代较为久远,约为2001年左右完成后经部分修改的,所以到处带着C时代的痕迹。ALLOC到处都有,内存管理异常繁琐,那时候的程序员真的好累啊!
二、核心代码介绍
最核心的函数就是
EnumerateHubPorts (
HTREEITEM hTreeParent,
HANDLE hHubDevice,
ULONG NumPorts
)
对USB设备的枚举都在这个函数中实现
success = DeviceIoControl(hHubDevice,
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,
connectionInfoEx,
nBytesEx,
connectionInfoEx,
nBytesEx,
&nBytesEx,
NULL);
用这个函数取得设备连接的信息,保存在 connectionInfoEx结构中
如果有设备连接这个端口,并且程序设置获取配置描述性信息,则
if (gDoConfigDesc &&
connectionInfoEx->ConnectionStatus == DeviceConnected)
{
configDesc = GetConfigDescriptor(hHubDevice,
index,
0);
}
gDoConfigDesc是全局变量,是由程序一个菜单项控制是否枚举设备的配置信息,默认为false。
GetConfigDescriptor (
HANDLE hHubDevice,
ULONG ConnectionIndex,
UCHAR DescriptorIndex
)
这个函数是核心中的核心,因为我们想要得到的信息基本上就靠这个了。
开始申请了一段内存,它的大小是 2个结构体大小之和。
UCHAR configDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) +
sizeof(USB_CONFIGURATION_DESCRIPTOR)];
看下面就明白了,前面保存的是 REQUEST,后面保存的是 CONFIGURATION_DESCRIPTOR类型
configDescReq = (PUSB_DESCRIPTOR_REQUEST)configDescReqBuf;
configDesc = (PUSB_CONFIGURATION_DESCRIPTOR)(configDescReq+1);
再看下一段代码,对configDescReq进行赋值,这个是什么呢,就是对USB设备进行查询的请求。
参考:http://www.usb.org/developers/docs/usb_31_031114.zip 9.2节
通过DeviceIOControl函数发送给USB设备,然后设备会进行回复,回复的信息保存在ConfigDescReq中。
//
// USBHUB uses URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE to process this
// IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION request.
//
// USBD will automatically initialize these fields:
// bmRequest = 0x80
// bRequest = 0x06
//
// We must inititialize these fields:
// wValue = Descriptor Type (high) and Descriptor Index (low byte)
// wIndex = Zero (or Language ID for String Descriptors)
// wLength = Length of descriptor buffer
//
configDescReq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8)
| DescriptorIndex;
configDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST));
// Now issue the get descriptor request.
//
success = DeviceIoControl(hHubDevice,
IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
configDescReq,
nBytes,
configDescReq,
nBytes,
&nBytesReturned,
NULL);
通过这个函数得到 回复信息的长度。
nBytes = sizeof(USB_DESCRIPTOR_REQUEST) + configDesc->wTotalLength;
然后再ALLOC 一块内存,用来保存配置信息
configDescReq = (PUSB_DESCRIPTOR_REQUEST)ALLOC(nBytes);
再次DeviceIoControl将获取的配置信息保存在configDescReq中。注意这个内存最前面的是请求,真正的信息是在后面一段。这个地方直接将这个地址返回
然后对这块信息进行处理, 注意看 configDesc+1的地方,把请求的信息跳过了。
AreThereStringDescriptors函数只是比较简单地检查是否存在描述信息。
存在的话,就通过GetAllStringDescriptors获取相关信息
if (configDesc != NULL &&
AreThereStringDescriptors(&connectionInfoEx->DeviceDescriptor,
(PUSB_CONFIGURATION_DESCRIPTOR)(configDesc+1)))
{
stringDescs = GetAllStringDescriptors(
hHubDevice,
index,
&connectionInfoEx->DeviceDescriptor,
(PUSB_CONFIGURATION_DESCRIPTOR)(configDesc+1));
}
GetAllStringDescriptors这个函数值得研究,因为它要从configDesc中取出我们需要的信息
函数后面通过一个while循环获取配置信息和接口信息。
PSTRING_DESCRIPTOR_NODE
GetAllStringDescriptors (
HANDLE hHubDevice,
ULONG ConnectionIndex,
PUSB_DEVICE_DESCRIPTOR DeviceDesc,
PUSB_CONFIGURATION_DESCRIPTOR ConfigDesc
)
{
PSTRING_DESCRIPTOR_NODE supportedLanguagesString;
PSTRING_DESCRIPTOR_NODE stringDescNodeTail;
ULONG numLanguageIDs;
USHORT *languageIDs;
PUCHAR descEnd;
PUSB_COMMON_DESCRIPTOR commonDesc;
//
// Get the array of supported Language IDs, which is returned
// in String Descriptor 0
//
supportedLanguagesString = GetStringDescriptor(hHubDevice,
ConnectionIndex,
0,
0);
if (supportedLanguagesString == NULL)
{
return NULL;
}
numLanguageIDs = (supportedLanguagesString->StringDescriptor->bLength - 2) / 2;
languageIDs = &supportedLanguagesString->StringDescriptor->bString[0];
stringDescNodeTail = supportedLanguagesString;
//
// Get the Device Descriptor strings
//
if (DeviceDesc->iManufacturer)
{
stringDescNodeTail = GetStringDescriptors(hHubDevice,
ConnectionIndex,
DeviceDesc->iManufacturer,
numLanguageIDs,
languageIDs,
stringDescNodeTail);
}
if (DeviceDesc->iProduct)
{
stringDescNodeTail = GetStringDescriptors(hHubDevice,
ConnectionIndex,
DeviceDesc->iProduct,
numLanguageIDs,
languageIDs,
stringDescNodeTail);
}
if (DeviceDesc->iSerialNumber)
{
stringDescNodeTail = GetStringDescriptors(hHubDevice,
ConnectionIndex,
DeviceDesc->iSerialNumber,
numLanguageIDs,
languageIDs,
stringDescNodeTail);
}
//
// Get the Configuration and Interface Descriptor strings
//
descEnd = (PUCHAR)ConfigDesc + ConfigDesc->wTotalLength;
commonDesc = (PUSB_COMMON_DESCRIPTOR)ConfigDesc;
while ((PUCHAR)commonDesc + sizeof(USB_COMMON_DESCRIPTOR) < descEnd &&
(PUCHAR)commonDesc + commonDesc->bLength <= descEnd)
{
switch (commonDesc->bDescriptorType)
{
case USB_CONFIGURATION_DESCRIPTOR_TYPE:
if (commonDesc->bLength != sizeof(USB_CONFIGURATION_DESCRIPTOR))
{
OOPS();
break;
}
if (((PUSB_CONFIGURATION_DESCRIPTOR)commonDesc)->iConfiguration)
{
stringDescNodeTail = GetStringDescriptors(
hHubDevice,
ConnectionIndex,
((PUSB_CONFIGURATION_DESCRIPTOR)commonDesc)->iConfiguration,
numLanguageIDs,
languageIDs,
stringDescNodeTail);
}
(PUCHAR)commonDesc += commonDesc->bLength;
continue;
case USB_INTERFACE_DESCRIPTOR_TYPE:
if (commonDesc->bLength != sizeof(USB_INTERFACE_DESCRIPTOR) &&
commonDesc->bLength != sizeof(USB_INTERFACE_DESCRIPTOR2))
{
OOPS();
break;
}
if (((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->iInterface)
{
stringDescNodeTail = GetStringDescriptors(
hHubDevice,
ConnectionIndex,
((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->iInterface,
numLanguageIDs,
languageIDs,
stringDescNodeTail);
}
(PUCHAR)commonDesc += commonDesc->bLength;
continue;
default:
(PUCHAR)commonDesc += commonDesc->bLength;
continue;
}
break;
}
return supportedLanguagesString;
}
OK这些信息获取完毕,再判断连接的是hub 还是设备, 如果是Hub ,则继续对 hub进行枚举
if (EnumerateHub(hTreeParent,
extHubName,
connectionInfoEx,
configDesc,
stringDescs,
deviceDesc) == FALSE)
如果是设备,则建立一个数据结构
info = (PUSBDEVICEINFO) ALLOC(sizeof(USBDEVICEINFO));
用来保存设备信息
info->DeviceInfoType = DeviceInfo;
info->ConnectionInfo = connectionInfoEx;
info->ConfigDesc = configDesc;
info->StringDescs = stringDescs;
再把设备加入到左侧的树中
AddLeaf(hTreeParent,
(LPARAM)info,
leafName,
icon);
info作为一个指针传递给 HTREEITEM 中的lParam,您点击左侧某个设备的时候,就调用display.c中的相关函数 把info中的信息再次解析成文本显示出来
三、我在修改代码中的几个错误
1.c语言与c++有一些不同的地方。
C语言中可以强制进行指针类型转换,但是C++ 中指针类型转换后无法对指针进行赋值。
(PUCHAR)commonDesc += commonDesc->bLength;
这样的语句会报错,无法对左值赋值。因为这个地方的强制类型转换 将 commonDesc转换为 PUCHAR类型,成为一个值,而不是commonDesc这个变量,无法再进行+=操作。
在修改过程中,我使用了
commonDesc = reinterpret_cast<PUSB_COMMON_DESCRIPTOR> (commonDesc + commonDesc->bLength);
这个是不是看起来非常地正确,非也,必须把后面的commonDesc转换为PUCHAR类型才可以。
这个bug让我的程序出错花了2天才搞定。
2.内存管理
windows对代码质量的追求还是非常高的,这个程序里面用了非常多的ALLOC,要防止内存泄露非常困难。
在usbview.h中定义了
#if DBG
#define ALLOC(dwBytes) MyAlloc(_T(__FILE__), __LINE__, (dwBytes))
#define REALLOC(hMem, dwBytes) MyReAlloc((hMem), (dwBytes))
#define FREE(hMem) MyFree((hMem))
#define CHECKFORLEAKS() MyCheckForLeaks()
#else
在debug.c中对以上有所实现,将内存分配和回收改成自定义的函数,可以用来对内存泄露进行检查。
在usbview.c 中
在RefreshTree的时候,调用下面的函数,WalTree中 CleanupItem是一个函数指针。
if (ghTreeRoot)
{
WalkTree(ghTreeRoot, CleanupItem, 0);
TreeView_DeleteAllItems(ghTreeWnd);
ghTreeRoot = NULL;
}
在enum.c中
VOID
CleanupItem (
HWND hTreeWnd,
HTREEITEM hTreeItem
)
获取每个HTREEITEM中的 info指针,然后进行内存释放,确保不存在内存泄露。
附上我用vs2005 做的一个 usbview 的工程,需要安装winddk 7600. 可以编译通过。请各位自行调试查看。
我所做的只是新建一个vs工程,把usbview的文件都拖进来, 添加相应的头文件目录和链接库的目录。
http://download.csdn.net/download/v6543210/7649119
不得不说csdn 网站感觉非常的不稳定,上传非常的慢,有时候会出莫名其妙的错误。因为论坛老了?