在Windows操作系统中,有时候USB设备可能会遇到问题,需要重启。本文将介绍如何通过编程方式重启USB设备,以及在不同版本的Windows系统中需要注意的事项。
微软文档中提到,使用IOCTL_USB_HUB_CYCLE_PORT命令可以重启USB端口,从而重新枚举连接的设备。这个功能在WindowsXP中可用,但通常只对使用微软标准驱动的集线器有效。第三方驱动通常会返回ERROR_UNKNOWN_FUNCTION错误。在Vista和Windows 7中,微软的标准驱动不再支持这个功能,总是返回ERROR_NOT_SUPPORTED错误。从Windows 8开始,这个功能又可以使用了,但与XP不同,需要管理员权限。没有管理员权限的话,会返回ERROR_NOT_SUPPORTED错误,或者从Windows 10版本1903开始返回ERROR_GEN_FAILURE错误,这两者都很容易让人误解,因为实际上这是一个权限问题。
以下是一个C++函数,它接受USB设备的设备实例ID作为唯一的参数,并尝试重启该设备。
bool CycleUsbDevice(char* pszUsbDeviceId) {
// 第一步:在设备管理器中找到USB设备
DEVINST DevInst = 0;
if (CR_SUCCESS != CM_Locate_DevNode(&DevInst, pszUsbDeviceId, 0)) {
return false;
}
// 第二步:确定USB端口号
char szLocation[64] = "";
DWORD dwType = 0;
ULONG uLen = sizeof(szLocation);
if (CR_SUCCESS != CM_Get_DevNode_Registry_Property(DevInst, CM_DRP_LOCATION_INFORMATION, &dwType, szLocation, &uLen, 0)) {
return false;
}
if (0 != strncmp(szLocation, "Port_#", 6)) {
return false;
}
int PortNumber = atoi(szLocation + 6);
// 第三步:USB集线器是父设备
DEVINST DevInstHub = 0;
if (CR_SUCCESS != CM_Get_Parent(&DevInstHub, DevInst, 0)) {
return false;
}
// 第四步:请求USB集线器的“设备接口”即“DevicePath”
char szHubDevPath[MAX_PATH] = "";
char szHubDeviceID[MAX_DEVICE_ID_LEN];
if (CR_SUCCESS != CM_Get_Device_ID(DevInstHub, szHubDeviceID, MAX_DEVICE_ID_LEN, 0)) {
return false;
}
if (CR_SUCCESS != CM_Get_Device_Interface_List((GUID*)&GUID_DEVINTERFACE_USB_HUB, szHubDeviceID, szHubDevPath, sizeof(szHubDevPath), CM_GET_DEVICE_INTERFACE_LIST_PRESENT)) {
return false;
}
if (!szHubDevPath[0]) {
return false;
}
// 第五步:打开集线器
HANDLE hHub = CreateFile(szHubDevPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hHub) {
return false;
}
// 第六步:调用IOCTL_USB_HUB_CYCLE_PORT
typedef struct _USB_CYCLE_PORT_PARAMS {
ULONG ConnectionIndex;
ULONG StatusReturned;
} USB_CYCLE_PORT_PARAMS, *PUSB_CYCLE_PORT_PARAMS;
USB_CYCLE_PORT_PARAMS CyclePortParams = { PortNumber, 0 };
DWORD dwBytes;
int res = DeviceIoControl(hHub, IOCTL_USB_HUB_CYCLE_PORT, &CyclePortParams, sizeof(CyclePortParams), &CyclePortParams, sizeof(CyclePortParams), &dwBytes, NULL);
CloseHandle(hHub);
return (0 != res && 0 == CyclePortParams.StatusReturned);
}
微软的IOCTL_USB_HUB_CYCLE_PORT文档有误:它说“输出缓冲区:无”,这是错误的。USB_CYCLE_PORT_PARAMS结构体的StatusReturned成员表明,这个结构体是作为输入输出缓冲区使用的。它就是这样工作的,之后dwBytes包含8,这是预期的sizeof(USB_CYCLE_PORT_PARAMS)大小。
与通过设备管理器重启设备不同,IOCTL_USB_HUB_CYCLE_PORT不会请求权限,因此没有驱动程序可以否决它,所以它总是成功的。但是,端口必须有USB设备连接并且安装了驱动程序。否则,它会失败,并返回错误433 (ERROR_NO_SUCH_DEVICE) 或者从Windows10 21H2开始返回错误50 (ERROR_NOT_SUPPORTED),这同样很容易让人误解。Windows 11则给出了正确的错误1167 (ERROR_DEVICE_NOT_CONNECTED)。