【问题标题】:SerialPorts and WaitForMultipleObjectsSerialPorts 和 WaitForMultipleObjects
【发布时间】:2013-02-28 13:21:27
【问题描述】:

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

因此,串行协议的实现是针对操作系统和非操作系统系统的混合体,我不会触及实现本身。我想让它与现有的实现兼容。如果在合理的时间内失败了,我将创建一个单独的线程进行串行读取。

好的,基本上实现是打开串口,在我们的 IO 系统中注册文件描述符(在 Linux 上使用 epoll,在 Windows 上使用 WaitForMultipleObjects)然后,基本上,只是等待所有句柄并执行任何需要的操作.因此,当句柄发出读取信号时,我们希望从串行端口读取。不幸的是,在 Windows 上,您无法指定是等待读取还是写入,所以我想我会使用以下解决方案:

  • CreateFile 和 FILE_FLAG_OVERLAPPED
  • SetCommMask 和 EV_RXCHAR
  • 使用手动重置事件创建 OVERLAPPED 结构
  • 使用上述OVERLAPPED结构调用WaitCommEvent,通常返回ERROR_IO_PENDING

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

  • ReadFile
  • 如果成功,ResetEvent 并再次调用WaitCommEvent

然而,如果你指定FILE_FLAG_OVERLAPPED,你必须使用重叠的IO来读写。所以我想只要ReadFile或WriteFile返回ERROR_IO_PENDING,我就等着WaitForSingleObject和GetOverlappedResult的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_waitHandle 上调用WaitForMultipleObjects,这与OVERLAPPED 结构的hEvent 成员相同。这是在一个循环中完成的,并且列表中还有其他几个句柄,这就是为什么这与您有一个线程专门从串行端口读取的典型解决方案不同的原因。我基本上无法控制循环,这就是为什么我尝试在正确的时间执行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;
}

现在好像可以了。再也没有崩溃了,至少我无法重现它们。所以它现在有效,我只是在问我做的事情是否理智,或者我是否应该做不同的事情。

【问题讨论】:

  • 是的,这是可以做到的,而且这种做法很常见。很多话,但没有任何暗示你可能做错了什么。
  • @HansPassant:如果我知道我可能做错了什么,我不会问这个问题,对吗?如果这能让你感觉更好,我可以发布一百行代码。但是,我确实没有找到一个单一来源,有人按照我需要的方式进行操作。
  • 崩溃的性质是什么?
  • 您必须使用ReadFileEx 进行重叠 I/O。
  • @JonathanPotter:这不是真的。 ReadFile() 也支持重叠 I/O。 ReadFile()和ReadFileEx()的唯一区别是ReadFile()支持同步和异步I/O,而ReadFileEx()只支持异步I/O。

标签: winapi serial-port waitformultipleobjects


【解决方案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;
}

【讨论】:

  • 谢谢,我写的代码或多或少是匆忙创建的,因为我只是想让事情运行起来;)
  • 不幸的是,我们仍然在某些机器上遇到 0xC0000005 崩溃,主要是在对 SerialPort::ReadData 的调用中,有时在 ResetEvent 调用中。在我见过的所有情况下,我们都是从外部来自 WaitForMultipleObjectsEx 的,因此事件已发出信号。 ReadData is 也可能被一个定时器调用,它只是检查那里是否有东西(以避免电报停留在内部缓冲区中,直到更多的 IO 到来)。定时器永远不能与 IO 同时触发。离开 Read/WriteData 时可能无法访问某些内容吗?我希望代码是同步的。
  • 现在很奇怪。我尝试禁用计时器,使用本地缓冲区而不是给定的缓冲区,但我们仍然崩溃。使用上面的代码,我们现在在GetOverlappedResult 调用中大多会在WriteData 中崩溃。 dwBytesWritten 已经在崩溃点包含一个值(如 10 或 17),因此看来 GetOverlappedResult 已准备好返回。如果我们在函数中更改一些内容,调试器会在 WriteData 调用之外停止,但对 ntdll 和 KernelBase 有一个很深的堆栈。就好像堆栈有问题一样。 必须这段代码有问题;)
  • 看来我明白了。我重新阅读了所有文档并发现了一件有趣的事情:WaitCommEvent 写入我提供的 dwEventMask。一旦等待完成,它就会这样做。但是由于我在函数中进行了调用,所以它稍后就消失了 - 导致堆栈损坏。所有示例都没有问题,因为它们有一个带有局部变量的专用循环......将在它一直发生的机器上进行测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多