繁体   English   中英

USB ReadFile() 替换串行通信设备使用的串行端口代码超时

[英]time out for USB ReadFile() replacing Serial Port code used with serial communications device

我正在为热敏收据打印机扩展收据打印串行端口 (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设备)。 这意味着需要一些其他机制来提供一种方法来允许由于使用 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);
}

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

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

我还在 codeproject.com 上找到了由 Chuan-Liang Teng 撰写的 Enumering 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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM