某项目中,设备与PC之间通过USB Bulk模式进行数据传输,PC端的APP跑在Win10上,跟设备通信这部分原本是基于libusb开发的,运行稳定。后来考虑到PC端APP只有for Windows一个版本,使用libusb的意义不大(libusb最大的好处是跨平台),因此打算去掉libusb,直接基于微软WinUSB实现与设备通信(在Windows上libusb缺省也是基于WinUSB实现的)。
libusb中用于bulk通信的API函数libusb_bulk_transfer有一个timeout参数,去掉libusb后直接调用WinUSB的API函数WinUsb_ReadPipe和WinUsb_WritePipe来实现bulk读写,但是这两个函数没有timeout参数,缺省方式调用这两个函数如果没有完成操作(例如WinUsb_ReadPipe一直读不到数据)就会无限期等待而不返回,这不符合我们的要求。不过这两个函数都提供了一个Overlapped参数用于实现异步操作方式,标准调用流程如下(以bulk读为例):
DWORD WinUSBBulkRead(WINUSB_INTERFACE_HANDLE hWinusb, UCHAR endpoint, UCHAR *data, DWORD length, DWORD timeout)
{
DWORD ret = ERROR_SUCCESS;
OVERLAPPED ov;
DWORD n;
ResetEvent(&eventReadPipe);
memset(&ov, 0, sizeof(ov));
ov.hEvent = eventReadPipe;
if (!WinUsb_ReadPipe(hWinusb, endpoint, data, length, NULL, &ov)) {
ret = GetLastError();
if (ret == ERROR_IO_PENDING) {
ret = WaitForSingleObject(eventReadPipe, timeout);
if (ret == WAIT_OBJECT_0) {
ret = ERROR_SUCCESS;
}
else if (ret == WAIT_TIMEOUT) {
ret = ERROR_TIMEOUT;
}
else {
ret = GetLastError();
}
}
}
if (ret == ERROR_SUCCESS) {
if (!WinUsb_GetOverlappedResult(hWinusb, &ov, &n, FALSE)) {
ret = GetLastError();
}
}
return ret;
}
然而上面这段代码在实际运行中发现不稳定,跑一段时间以后经常会出现接收超时,网上查到的参考代码也都是这么写的,不知道问题在哪里。查看libusb源码中调用WinUSB的部分,在调用WinUsb_ReadPipe和WinUsb_WritePipe时用的也是Overlapped方式,但对timeout的处理不太一样,因代码太复杂,没有时间仔细看。这时在微软文档上看到WinUsb_SetPipePolicy可以设置的Policy里面有一个PIPE_TRANSFER_TIMEOUT,之前初始化WinUSB调用WinUsb_SetPipePolicy设置Policy时这个PIPE_TRANSFER_TIMEOUT设的是0,从文档描述看这个值如果设成非0就可以实现我们想要的timeout的效果,而WinUsb_ReadPipe和WinUsb_WritePipe那里不再使用Overlapped方式,代码也因此更简单。试了一下果然可以,而且运行很稳定。
微软文档里对将PIPE_TRANSFER_TIMEOUT设成非0值有一个说明:
A minor performance penalty will occur due to timer management.
但是实际测下来的性能比之前用libusb还要更好一些,能满足我们的要求。