SerialPorts和WaitForMultipleObjects

时间:2013-02-28 13:21:28

标签: winapi serial-port waitformultipleobjects

我在跨平台应用程序(使用Linux嵌入式和实际嵌入式目标)中遇到串口问题,这也适用于Windows,使开发更容易。这是关于Windows的实现。

因此,串行协议的实现针对OS和非OS系统的混合,我不会触及实现本身。我想使它与现有的实现兼容。如果在合理的时间内失败,我将只为串行阅读创建一个单独的线程。

好的,基本上实现打开串口,在我们的IO系统中注册文件描述符(在Linux上使用epoll,在Windows上使用WaitForMultipleObjects)然后,基本上,等待所有句柄并做任何要求。所以我们想要在读取手柄信号时从串口读取。不幸的是,在Windows上,您无法指定是否在等待读取或写入,因此我认为我将使用以下解决方案:

  • CreateFileFILE_FLAG_OVERLAPPED
  • SetCommMaskEV_RXCHAR
  • 使用手动重置事件
  • 创建OVERLAPPED结构
  • 使用WaitCommEvent结构调用OVERLAPPED,通常会返回ERROR_IO_PENDING

这是基本设置。我注册事件句柄而不是文件句柄等待。当手柄发出信号时,我会执行以下操作:

  • ReadFile
  • 如果成功,ResetEvent并再次致电WaitCommEvent

然而,似乎如果指定FILE_FLAG_OVERLAPPED必须使用重叠IO进行读写。所以我认为只要ReadFileWriteFile返回ERROR_IO_PENDING,我就会等待WaitForSingleObjectGetOverlappedResult的IO。看来我并没有深入研究。它似乎基本上有效,但有时它会在ResetEvent个调用之一崩溃,好像重叠仍然有效(虽然我猜它仍然不应该崩溃)。

所以,实际的问题。这可以按我的意愿完成吗?这种方法一般存在问题,还是应该有效?或者使用另一个线程唯一的好解决方案?通信已经在一个单独的线程中,因此它至少会有三个线程。


我会尝试根据需要发布尽可能多的代码,但它会从包含许多与串行阅读无直接关系的实际代码中减少。

SerialPort::SerialPort(const std::string &filename)
{
    fd = INVALID_HANDLE_VALUE;
    m_ov = new OVERLAPPED(); // Pointer because header shouldn't include Windows.h.
    memset(m_ov, 0, sizeof(OVERLAPPED));
    m_waitHandle = m_ov->hEvent = CreateEvent(0, true, 0, 0);
}

SerialPort::~SerialPort(void)
{
    Close();
    CloseHandle(m_ov->hEvent);
    delete m_ov;
}

构造函数在一个单独的线程中调用,后来调用Open:

bool SerialPort::Open(void)
{
    if (fd != INVALID_HANDLE_VALUE)
        return true;
    fd = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (fd != INVALID_HANDLE_VALUE) {
        DCB dcb;
        ZeroMemory(&dcb, sizeof(DCB));

        COMMTIMEOUTS timeouts = {0};
        timeouts.ReadIntervalTimeout = TimeOut();
        timeouts.ReadTotalTimeoutConstant = TimeOut();
        timeouts.ReadTotalTimeoutMultiplier = TimeOut() / 5;
        if (timeouts.ReadTotalTimeoutMultiplier == 0) {
            timeouts.ReadTotalTimeoutMultiplier = 1;
        }

        if (!SetCommTimeouts(fd, &timeouts)) {
            DebugBreak();
        }
        SetCommMask(fd, EV_RXCHAR);
        InitWait();

        return true;
    }
    return false;
}

void SerialPort::InitWait()
{
    if (WaitForSingleObject(m_ov->hEvent, 0) == WAIT_OBJECT_0) {
        return; // Still signaled
    }         
    DWORD dwEventMask;
    if (!WaitCommEvent(fd, &dwEventMask, m_ov)) {
        // For testing, I have some prints here for the different cases.
    }
}

通过相当长的链,线程然后在m _ WaitForMultipleObjects上调用waitHandle,这与hEvent结构的OVERLAPPED成员相同。这是在循环中完成的,并且列表中还有其他几个句柄,这就是为什么这与只有线程从串行端口读取的典型解决方案不同的原因。我基本上无法控制循环,这就是为什么我会在恰当的时间尝试WaitCommEvent(在InitWait内)。

当发出句柄信号时,线程调用ReadData方法:

int SerialPort::ReadData(void *buffer, int size)
{
    if (fd != INVALID_HANDLE_VALUE) {
        // Timeouts are reset here to MAXDWORD/0/0, not sure if necessary.
        DWORD dwBytesRead;
        OVERLAPPED ovRead = {0};
        ovRead.hEvent = CreateEvent(0, true, 0, 0);
        if (ReadFile(fd, buffer, size, &dwBytesRead, &ovRead)) {
            if (WaitForSingleObject(m_ov->hEvent, 0) == WAIT_OBJECT_0) {
                // Only reset if signaled, because we might get here because of a timer.
                ResetEvent(m_waitHandle);
                InitWait();
            }
            CloseHandle(ovRead.hEvent);
            return dwBytesRead;
        } else {
            if (GetLastError() == ERROR_IO_PENDING) {
                WaitForSingleObject(ovRead.hEvent, INFINITE);
                GetOverlappedResult(fd, &ovRead, &dwBytesRead, true);
                InitWait();
                CloseHandle(ovRead.hEvent);
                return dwBytesRead;
            }
        }
        InitWait();
        CloseHandle(ovRead.hEvent);
        return -1;
    } else {
        return 0;
    }
}

写入操作如下,无需同步:

int SerialPort::WriteData(const void *buffer, int size)
{
    if (fd != INVALID_HANDLE_VALUE) {
        DWORD dwBytesWritten;
        OVERLAPPED ovWrite = {0};
        ovWrite.hEvent = CreateEvent(0, true, 0, 0);
        if (!WriteFile(fd, buffer, size, &dwBytesWritten, &ovWrite)) {
            if (GetLastError() == ERROR_IO_PENDING) {
                WaitForSingleObject(ovWrite.hEvent, INFINITE);
                GetOverlappedResult(fd, &ovWrite, &dwBytesWritten, true);
                CloseHandle(ovWrite.hEvent);
                return dwBytesWritten;
            } else {
                CloseHandle(ovWrite.hEvent);
                return -1;
            }
        }
        CloseHandle(ovWrite.hEvent);
    }
    return 0;
}

现在似乎确实有效。没有崩溃了,至少我无法重现它们。因此,现在它起作用,我只是问我做的是否理智,或者我是否应该以不同的方式做事。

1 个答案:

答案 0 :(得分:1)

副手,我在您显示的代码中没有看到任何错误,但我想建议替代代码来清除ReadData()WriteData()中的错误处理:

int SerialPort::ReadData(void *buffer, int size)
{
    if (fd == INVALID_HANDLE_VALUE)
        return 0;

    OVERLAPPED ovRead = {0};
    ovRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ovRead.hEvent)
        return -1;

    DWORD dwBytesRead;
    if (!ReadFile(fd, buffer, size, &dwBytesRead, &ovRead))
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
            CloseHandle(ovRead.hEvent);
            return -1;
        }

        if (!GetOverlappedResult(fd, &ovRead, &dwBytesRead, TRUE))
        {
            CloseHandle(ovRead.hEvent);
            return -1;
        }
    }

    if (WaitForSingleObject(m_waitHandle, 0) == WAIT_OBJECT_0)
    {
        ResetEvent(m_waitHandle);
        InitWait();
    }

    CloseHandle(ovRead.hEvent);
    return dwBytesRead;
}

int SerialPort::WriteData(const void *buffer, int size)
{
    if (fd == INVALID_HANDLE_VALUE)
        return 0;

    OVERLAPPED ovWrite = {0};
    ovWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ovWrite.hEvent)
        return -1;

    DWORD dwBytesWritten;
    if (!WriteFile(fd, buffer, size, &dwBytesWritten, &ovWrite))
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
            CloseHandle(ovWrite.hEvent);
            return -1;
        }

        if (!GetOverlappedResult(fd, &ovWrite, &dwBytesWritten, TRUE))
        {
            CloseHandle(ovWrite.hEvent);
            return -1;
        }
    }

    CloseHandle(ovWrite.hEvent);
    return dwBytesWritten;
}