【问题标题】:time out for USB ReadFile() replacing Serial Port code used with serial communications deviceUSB ReadFile() 超时替换串行通信设备使用的串行端口代码
【发布时间】:2021-12-18 01:24:41
【问题描述】:

我正在为热敏收据打印机扩展收据打印串行端口 (COM) 接口,以便在不需要虚拟串行端口的情况下使用 USB 接口。我有一个工作原型,它将枚举连接的 USB 设备,找到具有特定供应商 ID 和产品 ID 的设备的 USB 路径,并使用 CreateFile() 打开与设备的连接。

现有的串行端口代码使用封装在一组函数中的 Windows API。我采用的方法是使用相同的函数集添加额外的代码,但依赖于 USB 连接而不是串行端口连接。我以前使用相同的方法允许通过串行端口或 WiFi/LAN 连接使用厨房打印机,而对现有代码的更改最少。

不幸的是,使用函数库的现有代码依赖于使用 ReadFile() 的函数并指定了超时,因此如果热敏打印机在合理的时间内没有响应状态请求,应用程序可以将其标记为关闭并允许操作继续或使用备份或辅助打印机。

如何为来自CreateFile() 的文件句柄上的ReadFile() 指定超时,该文件句柄打开使用USB 路径名的通信设备的连接?

需要考虑的是,这是用于多个串行通信设备(收据打印机、厨房打印机、秤等)的多线程代码,但是线程将拥有对特定设备的独占访问权限(厨房打印功能打开串行端口仅限厨房打印机,秤读取功能打开串行端口仅用于秤等)。

在现有的串行端口代码中,用于为使用CreateFile() 打开的串行端口连接设置超时的函数SetCommTimeouts() 不适用于使用CreateFile() 打开的USB 连接(请参阅SetupComm, SetCommState, SetCommTimeouts fail with USB device)。这意味着需要一些其他机制来提供一种方法来允许由于使用 USB 设备路径名时超时而导致 I/O 失败。

我们正在使用以下代码段来打开一个串行端口,无论是硬件 COM 端口还是模拟硬件 COM 端口的虚拟串行端口:

// see Microsoft document HOWTO: Specify Serial Ports Larger than COM9.
// https://support.microsoft.com/en-us/kb/115831
// CreateFile() can be used to get a handle to a serial port. The "Win32 Programmer's Reference" entry for "CreateFile()"
// mentions that the share mode must be 0, the create parameter must be OPEN_EXISTING, and the template must be NULL. 
//
// CreateFile() is successful when you use "COM1" through "COM9" for the name of the file;
// however, the value INVALID_HANDLE_VALUE is returned if you use "COM10" or greater. 
//
// If the name of the port is \\.\COM10, the correct way to specify the serial port in a call to
// CreateFile() is "\\\\.\\COM10".
//
// NOTES: This syntax also works for ports COM1 through COM9. Certain boards will let you choose
//        the port names yourself. This syntax works for those names as well.
wsprintf(wszPortName, TEXT("\\\\.\\COM%d"), usPortId);

/* Open the serial port. */
/* avoid to failuer of CreateFile */
for (i = 0; i < 10; i++) {
    hHandle = CreateFile (wszPortName, /* Pointer to the name of the port, PifOpenCom() */
                      GENERIC_READ | GENERIC_WRITE,  /* Access (read-write) mode */
                      0,            /* Share mode */
                      NULL,         /* Pointer to the security attribute */
                      OPEN_EXISTING,/* How to open the serial port */
                      0,            /* Port attributes */
                      NULL);        /* Handle to port with attribute */
                                    /* to copy */

    /* If it fails to open the port, return FALSE. */
    if ( hHandle == INVALID_HANDLE_VALUE )   {    /* Could not open the port. */
        dwError = GetLastError ();
        if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_INVALID_NAME || dwError == ERROR_ACCESS_DENIED) {
            LeaveCriticalSection(&g_SioCriticalSection);
            // the COM port does not exist. probably a Virtual Serial Communications Port
            // from a USB device which was either unplugged or turned off.
            // or the COM port or Virtual Serial Communications port is in use by some other application.
            return PIF_ERROR_COM_ACCESS_DENIED;
        }
        PifLog (MODULE_PIF_OPENCOM, LOG_ERROR_PIFSIO_CODE_01);
        PifLog (MODULE_ERROR_NO(MODULE_PIF_OPENCOM), (USHORT)dwError);
        PifLog(MODULE_DATA_VALUE(FAULT_AT_PIFOPENCOM), usPortId);
        PifSleep(500);
    } else {
        break;
    }
}
if ( hHandle == INVALID_HANDLE_VALUE )   {    /* Could not open the port. */
    wsprintf(wszDisplay, TEXT("CreateFile, COM%d, Last Error =%d\n"), usPortId, dwError);
    OutputDebugString(wszDisplay);
    LeaveCriticalSection(&g_SioCriticalSection);
    return PIF_ERROR_COM_ERRORS;
}

/* clear the error and purge the receive buffer */
dwError = (DWORD)(~0);                  // set all error code bits on
ClearCommError(hHandle, &dwError, NULL);
PurgeComm( hHandle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

ReadFile() 封装在一个函数中,如下所示:

fResult = ReadFile(hHandle, pBuffer, (DWORD)usBytes, &dwBytesRead, NULL);

if (PifSioCheckPowerDown(usPort, aPifSioTable) == TRUE) {
    return PIF_ERROR_COM_POWER_FAILURE;
}

if (fResult) {
    if (!dwBytesRead) return PIF_ERROR_COM_TIMEOUT;
    return (SHORT)dwBytesRead;
} else {
    SHORT  sErrorCode = 0;     // error code from PifSubGetErrorCode(). must call after GetLastError().
    dwError = GetLastError();
    PifLog (MODULE_PIF_READCOM, LOG_ERROR_PIFSIO_CODE_06);
    PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)dwError);
    sErrorCode = PifSubGetErrorCode(hHandle);
    PifLog (MODULE_ERROR_NO(MODULE_PIF_READCOM), (USHORT)abs(sErrorCode));
    PifLog (MODULE_DATA_VALUE(MODULE_PIF_READCOM), usPort);
    return (sErrorCode);
}

【问题讨论】:

    标签: c++ winapi serial-port usb readfile


    【解决方案1】:

    我发现许多类似的已发布问题都涉及管道,但使用重叠 I/O 的方法相同。

    我还在网络Peter's blog: Getting a handle on usbprint.sys 上找到了以下文章,该文章提供了有关如何查找 USB 连接设备的 USB 路径名的代码和说明。我在下面的课程中使用了一些代码示例。

    我还在 codeproject.com 上找到了 Chuan-Liang Teng 的文章Enumerating windows device,其中包含枚举连接的 USB 设备并询问有关设备的各种设置和详细信息的示例。那篇文章中的代码虽然很旧,但对于这个特定的应用程序来说虽然不是必需的,但很有帮助。

    我有一个使用重叠 I/O 的原型 C++ 类,它似乎正在复制使用 USB 连接到热敏打印机的串行端口连接所看到的行为。完整的源代码和 Visual Studio 2017 解决方案和项目文件位于我的 GitHub 存储库 https://github.com/RichardChambers/utilities_tools/tree/main/UsbWindows,因为此片段包含最相关的部分。

    我已经在销售点应用程序中使用修改后的代码进行了一个简单的测试,现在正在将其集成到现有的热敏收据打印机源代码中,该源代码已经与串行端口一起使用。

    #include <windows.h>
    #include <setupapi.h>
    #include <initguid.h>
    
    #include <iostream>    
    
    // This is the GUID for the USB device class.
    // It is defined in the include file Usbiodef.h of the Microsoft Windows Driver Kit.
    // See also https://msdn.microsoft.com/en-us/library/windows/hardware/ff545972(v=vs.85).aspx which
    // provides basic documentation on this GUID.
    DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE,  0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);
            
    
    class UsbSerialDevice
    {
    public:
        // See https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-server-using-overlapped-i-o?redirectedfrom=MSDN
        // to implement time outs for Write and for Read.
        UsbSerialDevice(const wchar_t* wszVendorIdIn = nullptr);
        ~UsbSerialDevice();
        int CreateEndPoint(const wchar_t* wszVendorId = nullptr, DWORD dwDesiredAccess = (GENERIC_READ | GENERIC_WRITE));
        void CloseEndPoint(void);
        int ListEndPoint(const wchar_t* wszVendorIdIn);
        int ReadStream(void* bString, size_t nBytes);
        int WriteStream(void* bString, size_t nBytes);
        DWORD  SetWriteTimeOut(DWORD msTimeout);
        DWORD  SetReadTimeOut(DWORD msTimeout);
    
        DWORD     m_dwError;          // GetLastError() for last action
        DWORD     m_dwErrorWrite;     // GetLastError() for last write
        DWORD     m_dwErrorRead;      // GetLastError() for last read
        DWORD     m_dwBytesWritten;   // number of bytes last write
        DWORD     m_dwBytesRead;      // number of bytes last read
        DWORD     m_dwWait;           // WaitForSingleObject() return value
    
    private:
        HANDLE        m_hFile;
        OVERLAPPED    m_oOverlap;
        COMMTIMEOUTS  m_timeOut;
        const unsigned short m_idLen = 255;
        wchar_t  m_wszVendorId[255 + 1] = { 0 };
    };
    
    UsbSerialDevice::UsbSerialDevice(const wchar_t* wszVendorIdIn) :
        m_dwError(0),
        m_dwErrorWrite(0),
        m_dwErrorRead(0),
        m_dwBytesWritten(0),
        m_dwBytesRead(0),
        m_dwWait(0),
        m_hFile(INVALID_HANDLE_VALUE)
    {
        memset(&m_oOverlap, 0, sizeof(m_oOverlap));
        m_oOverlap.hEvent = INVALID_HANDLE_VALUE;
    
        if (wszVendorIdIn != nullptr) ListEndPoint(wszVendorIdIn);
    }
    
    void UsbSerialDevice::CloseEndPoint(void )
    {
        if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) CloseHandle(m_hFile);
        if (m_oOverlap.hEvent && m_oOverlap.hEvent != INVALID_HANDLE_VALUE) CloseHandle(m_oOverlap.hEvent);
    }
    
    UsbSerialDevice::~UsbSerialDevice()
    {
        CloseEndPoint();
    }
    
    
    /*
     *  Returns:  -1 - file handle is invalid
     *             0 - write failed. See m_dwErrorWrite for GetLastError() value
     *             1 - write succedded.
    */
    int UsbSerialDevice::WriteStream(void* bString, size_t nBytes)
    {
        SetLastError(0);
        m_dwError = m_dwErrorWrite = 0;
        m_dwBytesWritten = 0;
        m_dwWait = WAIT_FAILED;
    
        if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) {
    
            BOOL  bWrite = WriteFile(m_hFile, bString, nBytes, 0, &m_oOverlap);
            m_dwError = m_dwErrorWrite = GetLastError();
    
            if (!bWrite && m_dwError == ERROR_IO_PENDING) {
    
                SetLastError(0);
                m_dwError = m_dwErrorWrite = 0;
    
                m_dwWait = WaitForSingleObject(m_oOverlap.hEvent, m_timeOut.WriteTotalTimeoutConstant);
    
                BOOL bCancel = FALSE;
    
                switch (m_dwWait) {
                case WAIT_OBJECT_0:  // The state of the specified object is signaled.
                    break;
                case WAIT_FAILED:    // The function has failed. To get extended error information, call GetLastError.
                    m_dwError = m_dwErrorWrite = GetLastError();
                    bCancel = CancelIo(m_hFile);
                    break;
                case WAIT_TIMEOUT:   // The time-out interval elapsed, and the object's state is nonsignaled.
                case WAIT_ABANDONED: // thread owning mutex terminated before releasing or signaling object.
                    bCancel = CancelIo(m_hFile);
                    m_dwError = m_dwErrorRead = ERROR_COUNTER_TIMEOUT;
                    break;
                }
    
                bWrite = GetOverlappedResult(m_hFile, &m_oOverlap, &m_dwBytesRead, FALSE);
            }
    
            return bWrite;  // 0 or FALSE if failed, 1 or TRUE if succeeded.
        }
    
        return -1;
    }
    
    /*
     *  Returns:  -1 - file handle is invalid
     *             0 - read failed. See m_dwErrorRead for GetLastError() value
     *             1 - read succedded.
    */
    int UsbSerialDevice::ReadStream(void* bString, size_t nBytes)
    {
        SetLastError(0);
        m_dwError = m_dwErrorRead = 0;
        m_dwBytesRead = 0;
        m_dwWait = WAIT_FAILED;
    
        if (m_hFile && m_hFile != INVALID_HANDLE_VALUE) {
    
            BOOL  bRead = ReadFile(m_hFile, bString, nBytes, &m_dwBytesRead, &m_oOverlap);
            m_dwError = m_dwErrorRead = GetLastError();
    
            if (!bRead && m_dwError == ERROR_IO_PENDING) {
                SetLastError(0);
                m_dwError = m_dwErrorRead = 0;
    
                m_dwWait = WaitForSingleObject(m_oOverlap.hEvent, m_timeOut.ReadTotalTimeoutConstant);
    
                BOOL bCancel = FALSE;
    
                switch (m_dwWait) {
                case WAIT_OBJECT_0:  // The state of the specified object is signaled.
                    break;
                case WAIT_FAILED:    // The function has failed. To get extended error information, call GetLastError.
                    m_dwError = m_dwErrorWrite = GetLastError();
                    bCancel = CancelIo(m_hFile);
                    break;
                case WAIT_TIMEOUT:   // The time-out interval elapsed, and the object's state is nonsignaled.
                case WAIT_ABANDONED: // thread owning mutex terminated before releasing or signaling object.
                    bCancel = CancelIo(m_hFile);
                    m_dwError = m_dwErrorRead = ERROR_COUNTER_TIMEOUT;
                    break;
                }
    
                bRead = GetOverlappedResult(m_hFile, &m_oOverlap, &m_dwBytesRead, FALSE);
            }
    
            return bRead;  // 0 or FALSE if failed, 1 or TRUE if succeeded.
        }
    
        return -1;
    }
    
    int UsbSerialDevice::ListEndPoint(const wchar_t* wszVendorIdIn)
    {
        m_dwError = ERROR_INVALID_HANDLE;
    
        if (wszVendorIdIn == nullptr) return 0;
    
        HDEVINFO    hDevInfo;
    
        // we need to make sure the vendor and product id codes are in lower case
        // as this is needed for the CreateFile() function to open the connection
        // to the USB device correctly. this lower case conversion applies to
        // any alphabetic characters in the identifier.
        //
        // for example "VID_0FE6&PID_811E" must be converted to "vid_0fe6&pid_811e"
    
        wchar_t  wszVendorId[256] = { 0 };
    
        for (unsigned short i = 0; i < 255 && (wszVendorId[i] = towlower(wszVendorIdIn[i])); i++);
    
        // We will try to get device information set for all USB devices that have a
        // device interface and are currently present on the system (plugged in).
        hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
        if (hDevInfo != INVALID_HANDLE_VALUE)
        {
            DWORD    dwMemberIdx;
            BOOL     bContinue = TRUE;
            SP_DEVICE_INTERFACE_DATA         DevIntfData;
    
            // Prepare to enumerate all device interfaces for the device information
            // set that we retrieved with SetupDiGetClassDevs(..)
            DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
            dwMemberIdx = 0;
    
            // Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
            // function causes GetLastError() to return  ERROR_NO_MORE_ITEMS. With each
            // call the dwMemberIdx value needs to be incremented to retrieve the next
            // device interface information.
            for (BOOL bContinue = TRUE; bContinue; ) {
                PSP_DEVICE_INTERFACE_DETAIL_DATA  DevIntfDetailData;
                SP_DEVINFO_DATA    DevData;
                DWORD  dwSize;
    
                dwMemberIdx++;
                SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);
    
                if (GetLastError() == ERROR_NO_MORE_ITEMS) break;
    
                // As a last step we will need to get some more details for each
                // of device interface information we are able to retrieve. This
                // device interface detail gives us the information we need to identify
                // the device (VID/PID), and decide if it's useful to us. It will also
                // provide a DEVINFO_DATA structure which we can use to know the serial
                // port name for a virtual com port.
    
                DevData.cbSize = sizeof(DevData);
    
                // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
                // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
                // of zero, and a valid RequiredSize variable. In response to such a call,
                // this function returns the required buffer size at dwSize.
    
                SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
    
                // Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
                // deallocate it later!
                DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
                DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
    
                if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
                {
                    if (wcsstr(DevIntfDetailData->DevicePath, wszVendorId)) {
                        wcscpy_s(m_wszVendorId, DevIntfDetailData->DevicePath);
                    }
                }
    
                HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
            }
    
            SetupDiDestroyDeviceInfoList(hDevInfo);
        }
    
        return 0;
    }
    
    int UsbSerialDevice::CreateEndPoint(const wchar_t* wszVendorIdIn, DWORD dwDesiredAccess)
    {
        if (wszVendorIdIn) {
            ListEndPoint(wszVendorIdIn);
        }
    
        m_dwError = ERROR_INVALID_HANDLE;
    
        // Finally we can start checking if we've found a useable device,
        // by inspecting the DevIntfDetailData->DevicePath variable.
        //
        // The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
        // \\?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
        //
        // The VID for a particular vendor will be the same for a particular vendor's equipment.
        // The PID is variable for each device of the vendor.
        //
        // As you can see it contains the VID/PID for the device, so we can check
        // for the right VID/PID with string handling routines.
    
        // See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h
    
        // See https://blog.peter.skarpetis.com/archives/2005/04/07/getting-a-handle-on-usbprintsys/
        // which describes a sample USB thermal receipt printer test application.
    
        SetLastError(0);
        m_hFile = CreateFile(m_wszVendorId, dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
        if (m_hFile == INVALID_HANDLE_VALUE) {
            m_dwError = GetLastError();
    //        wprintf(_T("   CreateFile() failed. GetLastError() = %d\n"), m_dwError);
        }
        else {
            m_oOverlap.hEvent = CreateEvent(
                NULL,    // default security attribute 
                TRUE,    // manual-reset event 
                TRUE,    // initial state = signaled 
                NULL);   // unnamed event object 
    
            m_timeOut.ReadIntervalTimeout = 0;
            m_timeOut.ReadTotalTimeoutMultiplier = 0;
            m_timeOut.ReadTotalTimeoutConstant = 5000;
            m_timeOut.WriteTotalTimeoutMultiplier = 0;
            m_timeOut.WriteTotalTimeoutConstant = 5000;
            m_dwError = 0;   // GetLastError();
            return 1;
        }
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2013-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多