c++ - 如何正确使用 COMMTIMEOUTS 和从串口读取的 OVERLAPPED IO 模式

标签 c++ winapi serial-port communication

我正在尝试在 Windows 7/8 X64 上使用重叠 IO 模式来模拟 Linux 的开放标志支持的非阻塞模式 (IO_NONBLOCK) 行为。此处的代码是跨平台串行 API 的窗口部分的一部分。

我可以使用 SerialCommWnt 对象的构造函数参数以阻塞或非阻塞 (OVERLAPPED) 模式打开 COMM 端口。就这个问题而言,我的所有问题都与何时以 OVERLAPPED 模式打开 COMM 端口有关(由流量控制构造函数参数指定)。对于 Read 方法,我指定了一个超时参数,该参数在从串行端口成功检索至少 1 个字节的数据后,应指示数据在串行通信的输入缓冲区中时 rTimeout 参数的剩余时间(我相信串行驱动程序在收到任何数据时通知重叠结构中的手动重置事件。

我阅读了很多关于如何处理这些 API 的 StackOverflow 帖子,其中很多都提到了 Microsoft Win32 API。到目前为止我能找到的最好的信息是

http://msdn.microsoft.com/en-us/library/ff802693.aspx 这个 API 对 Overlapped IO 造成混淆(特别是当涉及到在重叠模式下调用 ReadFile 时传递指向接收到的字节数的指针),但据我所知,它们都没有解决如何正确使用重叠 IO 的问题模式结合 COMMTIMEOUTS。在过去的几天里,我试验了 COMMTIMEOUTS 和与 ::WaitForSingleObject 一起使用的超时参数的设置组合。最后显示了似乎最有效的组合。关于与重叠 IO 结构和 COMMTIMEOUTS 关联的手动重置事件对象关联的超时,我有一些可靠性问题。我不完全确定,但似乎为了在从串行端口读取时超时正常工作,必须在 COMMTIMEOUTS 中指定超时。我尝试了一种组合,我在 SetCommTimeouts 中禁用了超时,而是在 ::WaitForSingleObject 的超时参数中使用了显式超时,但这没有用,相反我做了反过来,通过在 COMMTIMEOUTS 中指定超时并使用 ::WaitForSingleObject 方法调用指定 INFINITE。但是,我不确定是否存在这种情况会永远挂起的情况,如果是这样,我该如何处理。如果能提供有关如何正确处理此处可能挂起的任何信息,我将不胜感激。

这是我用来打开 COMM 端口的方法 - 在这种情况下,我有超时问题,我指定了 FILE_FLAG_OVERLAPPED。

    /**
     * Open the serial port using parameters set in theconstructor.<p>
     * The Port Number, Speed, Overlapped IO mode, #data bits &
     * async mode etc. are specified as constructor arguments.
     *
     * @return OS_FAILED, OS_SUCCESS
     */
    OsStatus
    SerialCommWnt::open()
    {
        // Critical Section
        std::lock_guard<std::recursive_mutex> lock (mMutexGuard);
        OsStatus result = OS_FAILED;
        std::ostringstream os;
        os << "\\\\.\\COM" << mCommPort;
        std::string deviceName = os.str();
        DWORD dwFlagsAndAttrs = (mFlowControl ==
            SerialCommBase::FCTL_OVERLAPPED)?
            FILE_FLAG_OVERLAPPED : 0;
        // open the underlying device for read and write
        mOsFileHandle = CreateFile (
            deviceName.c_str(),
            GENERIC_READ | GENERIC_WRITE,
            0,                      //(share) 0:cannot share the COM port
            NULL,                   // no security attributes
            OPEN_EXISTING,          // COMM devices must use OPEN_EXISTING
            dwFlagsAndAttrs,        // optional FILE_FLAG_OVERLAPPED
            NULL);                  // hTemplate must be NULL for comm devices
        if ( mOsFileHandle != INVALID_HANDLE_VALUE ) {
            // reserve an 8k communications channel buffer (both directions)
            BOOL isOK = SetupComm(mOsFileHandle, 8200, 8200);
            // Omit the call to SetupComm to use the default queue sizes.
            // Get the current configuration.
            DCB dcb;
            SecureZeroMemory(&dcb, sizeof(DCB));
            isOK = GetCommState (mOsFileHandle, &dcb);
            if (isOK) {
                // Fill in the DCB: baud=125000, 8 data bits, even parity, 1 stop bit.
                // This is the standard baud rate. The card we have has a custom crystal
                // changing this baud rate to 125K.
                dcb.BaudRate = static_cast<DWORD>(mBaudRate);
                dcb.ByteSize = static_cast<BYTE>(mByteSize);
                // enum values are ame as dcb.Parity defines
                dcb.Parity   = static_cast<BYTE>(mParity);
                dcb.fParity  = (mParity == SerialCommBase::PRTY_NONE)? FALSE : TRUE;
                dcb.StopBits = ONESTOPBIT;
                // ----------------------------------------------------
                // When running in win32 loopback with the simulator
                // in loopback mode, we must enable the RTS/CTS handshake
                // mode as there seems to be a 4K limit in the input
                // buffer when the DBU Simulator performs reads.
                // ----------------------------------------------------
                if (mFlowControl == SerialCommBase::FCTL_RTS_CTS) {
                    dcb.fOutxCtsFlow = 1;
                    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
                }
                // Not absolutely necessary as the DTR_CONTROL_DISABLE is default
                dcb.fDtrControl = DTR_CONTROL_DISABLE;
                isOK = SetCommState (mOsFileHandle, &dcb);
                if (isOK) {
                    COMMTIMEOUTS commTimeouts;
                    SecureZeroMemory(&commTimeouts, sizeof(COMMTIMEOUTS));
                    // These settings will cause ReadFile to return
                    // immediately if there is no data available at the port
                    // A value of MAXDWORD, combined with zero values for both the
                    // ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members,
                    // specifies that the read operation is to return immediately with
                    // the bytes that have already been received, even if no bytes
                    // have been received.
                    //isOK = GetCommTimeouts (mOsFileHandle, &CommTimeouts);
                    commTimeouts.ReadIntervalTimeout = MAXDWORD;
                    // ReadTotalTimeoutConstant - when set with a ms timeout value
                    // in conjunction with will ReadIntervalTimeout == MAXDWORD &&
                    // ReadTotalTimeoutMultiplier set to 0 be used to control the
                    // timeout for the read operation.   Each time the read with a
                    // timeout is called, we compare the existing timeouts in CommTimeouts
                    // before changing it.
                    commTimeouts.ReadTotalTimeoutConstant = 0;
                    commTimeouts.ReadTotalTimeoutMultiplier = 0;
                    // timeouts not used for write operations
                    commTimeouts.WriteTotalTimeoutConstant = 0;
                    commTimeouts.WriteTotalTimeoutMultiplier = 0;
                    isOK = SetCommTimeouts (mOsFileHandle, &commTimeouts);
                    if (isOK) {
                        // test for asynchronous mode
                        if (mFlowControl == SerialCommBase::FCTL_OVERLAPPED) {
                            // allocate & initialize overlapped
                            // structure support for rx & tx
                            mpOverlappedTx.reset(new(OVERLAPPED));
                            mpOverlappedRx.reset(new(OVERLAPPED));
                            if (mpOverlappedTx && mpOverlappedRx) {
                                SecureZeroMemory(mpOverlappedTx.get(), sizeof(OVERLAPPED));
                                SecureZeroMemory(mpOverlappedRx.get(), sizeof(OVERLAPPED));
                                // create an unsignaled manual reset (2nd Param TRUE)
                                // event used for GetOverlappedResult. This event will
                                // be signaled by the ReadFile to indicate when
                                // IO operations are complete or encounter errors
                                mpOverlappedTx->hEvent = CreateEvent(
                                    NULL, TRUE, FALSE, NULL);
                                if (mpOverlappedTx->hEvent != NULL) {
                                    // now do the same for the RX side
                                    mpOverlappedRx->hEvent = CreateEvent(
                                        NULL, TRUE, FALSE, NULL);
                                    if (mpOverlappedRx->hEvent != NULL) {
                                        setState(COMM_OPENED);
                                        result = OS_SUCCESS;
                                    } else {
                                        result = handleError(deviceName);
                                    }
                                } else {
                                    result = handleError(deviceName);
                                }
                                // close the handle and set error
                                if (result != OS_SUCCESS) {
                                    close();
                                    setState(COMM_OPEN_FAILED);
                                }
                            } else {
                                // close the handle and overlapped event
                                close();
                                setState(COMM_OPEN_FAILED);
                                result = OS_NO_MEMORY;
                            }
                        } else { // blocking mode
                            setState(COMM_OPENED);
                            result = OS_SUCCESS;
                        }
                    } else {
                        result = handleError(deviceName);
                        close();
                    }
                } else { // unable to set the baud rate or something
                    result = handleError(deviceName);
                    close();
                }
            }
        } else {
            result = handleError(deviceName);
            close();
        }
        return result;
    }

下面是执行定时读取的代码

    /**
     * Read a block of data into the specified raw buffer.
     * See http://msdn.microsoft.com/en-us/library/ms810467(v=MSDN.10).aspx
     * for details for Overlapped IO usage, in particular note that setting
     * the timeout each time is tricky.
     *
     * @param pData     [in/out] data buffer
     * @param rNumBytes [in] buffer size
     * @param rTimeout  [in/out] timeout specified in milliseconds.
     *                  This parameter is updated to reflect the
     *                  remaining time.
     * @param rNumBytesRead
     *                  [out] number of bytes read
     *
     * @return OS_SUCCESS, OS_WAIT_TIMEOUT, OS_INVALID_ARGUMENT or
     *         OS_FAILED
     */
    OsStatus
    SerialCommWnt::read(
        void* pData,
        const size_t& rNumBytes,
        milliseconds& rTimeout,
        size_t& rNumBytesRead)
    {
        OsStatus result = OS_WAIT_TIMEOUT;
        rNumBytesRead = 0;
        DWORD numBytesRead = 0;
        DWORD commError;
        COMSTAT commStatus;
        auto startTime = system_clock::now();
        if (mpOverlappedRx) {
            // update the timeout used for ReadFile - note that the
            // magic combination that works for an absolute timeout is
            // MAXDWORD, timeoutMS, 0.
            COMMTIMEOUTS commTimeouts;
            GetCommTimeouts(mOsFileHandle, &commTimeouts);
            if (commTimeouts.ReadTotalTimeoutConstant != rTimeout.count()) {
                commTimeouts.ReadIntervalTimeout = MAXDWORD;
                commTimeouts.ReadTotalTimeoutConstant =
                    static_cast<DWORD>(rTimeout.count());
                SetCommTimeouts (mOsFileHandle, &commTimeouts);
            }

            // asynchronous overlapped IO mode.
            // reset the manual event to the non-signaled.
            // No Need for this as ReadFile resets it by itself
            // ResetEvent(mpOverlappedRx->hEvent);
            BOOL isOK = ReadFile(
                mOsFileHandle, pData, (DWORD)rNumBytes,
                reinterpret_cast<DWORD*>(&rNumBytesRead),
                mpOverlappedRx.get());
            // get the result to date - only valid to call this
            // if ReadFile returns !isOK (FALSE) &&
            // last error set to ERROR_IO_PENDING
            //milliseconds elapsedTime;
            if (!isOK) {
                DWORD dwLastError = GetLastError();
                if (dwLastError == ERROR_IO_PENDING) {
                    // pending IO, wait to complete using the COMMTIMEOUTS timer.
                    // when the COMMTIMEOUTS timer expires it will signal the 
                    // manual mpOverlappedRx->hEvent
                    DWORD ovlStatus = ::WaitForSingleObject(
                        mpOverlappedRx->hEvent, static_cast<DWORD>(
                            /*rTimeout.count()*/INFINITE));
                    switch (ovlStatus) {
                    case WAIT_TIMEOUT:
                        // timeout - update the remaining time to 0
                        rTimeout = milliseconds::zero();
                        result = OS_WAIT_TIMEOUT;
                        //elapsedTime = duration_cast<milliseconds>(
                        //    system_clock::now() - startTime);
                        break;
                    case WAIT_OBJECT_0:
                        // now that we have some data avaialable
                        // read it from overlapped IO
                        isOK = ::GetOverlappedResult(
                            mOsFileHandle, mpOverlappedRx.get(),
                            reinterpret_cast<DWORD*>(&rNumBytesRead),
                            FALSE);
                        result = (isOK && rNumBytesRead>0)?
                            OS_SUCCESS : OS_FAILED;
                        //elapsedTime = duration_cast<milliseconds>(
                        //    system_clock::now() - startTime);
                        // update the remaing time (cannot be < 0)
                        rTimeout = std::max<milliseconds>(
                            rTimeout - duration_cast<milliseconds>(
                                system_clock::now() - startTime),
                            milliseconds::zero());
                        break;
                    default:
                        rTimeout = milliseconds::zero();
                        break;
                    }
                } else if (dwLastError == ERROR_HANDLE_EOF) {
                    ClearCommError(mOsFileHandle, &commError, &commStatus);
                    result = OS_FILE_EOF;
                } else {
                    ClearCommError(mOsFileHandle, &commError, &commStatus);
                    result = OS_FAILED;
                }
            } else { // Success
                //elapsedTime = duration_cast<milliseconds>(
                //    system_clock::now() - startTime);
                rTimeout = std::max<milliseconds>(
                    rTimeout - duration_cast<milliseconds>(
                        system_clock::now() - startTime),
                    milliseconds::zero());
                result = OS_SUCCESS;
            }
        } else { // sync mode
            BOOL isOK = ReadFile ( mOsFileHandle, pData, (DWORD)rNumBytes,
                reinterpret_cast<LPDWORD>(&numBytesRead), NULL);
            if ( isOK && (numBytesRead > 0) ) {
                rNumBytesRead = (size_t) numBytesRead;
                result = OS_SUCCESS;
            } else {
                ClearCommError(mOsFileHandle, &commError, &commStatus);
                // @JC Changed from simple test if lpErrors == 9)
                // which is equivalent to (CE_BREAK | CE_RXOVER)
                //if ((lpErrors & (CE_BREAK | CE_FRAME | CE_OVERRUN |
                //     CE_RXOVER | CE_RXPARITY)) != 0x00) {
                if (commError == 9) {
                    result = OS_FAILED;
    //              printf ("ClearCommError - lpErrors[%02x]", lpErrors);
                }
            }
            // update the remaing time (cannot be < 0)
            rTimeout = std::max<milliseconds>(
                rTimeout - duration_cast<milliseconds>(
                    system_clock::now() - startTime),
                milliseconds::zero());
        }
        return result;
    }

最佳答案

    if (dwLastError == ERROR_IO_PENDING) {
        DWORD ovlStatus = ::WaitForSingleObject(mpOverlappedRx->hEvent, ...);
        //...
    }

当程序员使用重叠 I/O 时,这是一个非常常见的错误。核心思想是您使用它允许设备驱动程序在第一次调用 ReadFile() 时开始处理作业。这将需要一段时间,I/O 总是如此,尤其是串行端口,因为它们是非常慢的设备。

所以你问司机“开始吧”,它就开始工作了。最终,驱动程序将通过调用 OVERLAPPED.hEvent 上的 SetEvent() 方法来发出完成信号。这将完成您的 WaitForSingleObject() 调用。

当驱动程序处理它时,您应该做的是其他。您的线程应该做的另一项工作,在驱动程序处理 I/O 请求时很有用。例如,您可以用它点亮 MsgWaitForMultipleObjects()。它会触发一个消息循环,因此您的 UI 仍然可以响应。并且还会告诉您串口何时有可用的新数据。

代码中的缺陷在于您不知道还能做什么。它立即调用 WaitForSingleObject() 以等待重叠 I/O 完成。在驱动程序处理读取请求时阻塞线程而不做任何有用的工作。这是一个非常普遍的问题。

换句话说,您还没有找到使用重叠 I/O 的充分理由。通过使用同步 ReadFile() 调用,您将获得完全相同的结果。它会像您当前的代码一样阻塞,直到串行端口有可用数据。

所以不要理会它。也解决了超时问题。

关于c++ - 如何正确使用 COMMTIMEOUTS 和从串口读取的 OVERLAPPED IO 模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18216940/

相关文章:

c++ - 将 key 和 iv 保存到文件 AES 实现 Crypto++

c++ - 从文件读取时如何创建对象 vector ? [C++]

C++ Win API 函数 'not declared in this scope'

windows - 从我的应用程序发送消息 Ctrl+Alt+Del

c++ - 如何检测窗口已经最大化?

c++ - 如何直接从构造函数结束 C++ 代码?

c++ - 如何在 DLL 中隐藏导出函数

linux - 在 Linux 串口上接收/读取 BREAK 条件

bash - 在 bash 中将十六进制字节发送到串行

ubuntu - 在 ubuntu 上串行连接到 Intel Galileo