Winapi SerialPorts和WaitForMultipleObjects

Winapi SerialPorts和WaitForMultipleObjects,winapi,serial-port,waitformultipleobjects,Winapi,Serial Port,Waitformultipleobjects,我在跨平台应用程序(Linux嵌入式和实际嵌入式目标)的串行端口方面遇到了一些问题,该应用程序也可以在Windows上工作,以简化开发。这是关于Windows实现的 因此,串行协议的实现针对的是操作系统和非操作系统的混合系统,我将不涉及实现本身。我想使它与现有的实现兼容。如果在合理的时间内失败,我将只做一个单独的线程进行串行读取 SerialPort::SerialPort(const std::string &filename) { fd = INVALID_HANDLE_VA

我在跨平台应用程序(Linux嵌入式和实际嵌入式目标)的串行端口方面遇到了一些问题,该应用程序也可以在Windows上工作,以简化开发。这是关于Windows实现的

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

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

  • CreateFile
    文件标志重叠
  • SetCommMask
    with
    EV\u RXCHAR
  • 使用手动重置事件创建一个重叠的结构
  • 调用
    WaitCommEvent
    ,使用所述的
    重叠结构,该结构通常返回
    ERROR\u IO\u PENDING
这是基本的设置。我注册事件句柄而不是要等待的文件句柄。当手柄发出信号时,我执行以下操作:

  • ReadFile
  • 如果成功,
    ResetEvent
    并再次调用
    WaitCommEvent
然而,如果您指定
文件\u标志\u重叠
,那么您也必须使用重叠IO进行读写。因此,我认为无论何时
ReadFile
WriteFile
返回
ERROR\u IO\u 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
,这与重叠的
结构的
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;
}

看来它现在确实起作用了。现在已经没有崩溃了,至少我无法重现。因此,当它现在起作用时,我只是想问我所做的是否明智,或者我是否应该做不同的事情。

顺便说一句,我看不到您显示的代码中有任何错误,但我想建议使用其他代码来清理
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;
}

顺便说一句,我看不到您显示的代码中有任何错误,但我想建议您使用其他代码来清理
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;
}

是的,这是可以做到的,这是非常普遍的做法。很多话,但没有任何暗示你可能做错了什么。@HansPassant:如果我知道我可能做错了什么,我不会问这个问题,是吗?如果这能让你感觉更好的话,我可以发布上百行代码。但是,我没有找到任何一个源,有人按照我需要的方式进行操作。崩溃的性质是什么?对于重叠I/O,您必须使用
ReadFileEx
。@JonathanPotter:这不是真的
ReadFile()
还支持重叠I/O。
ReadFile()
ReadFileEx()
之间唯一的区别是
ReadFile()
支持同步和异步I/O,而
ReadFileEx()
只支持异步I/O。是的,这是可以做到的,这是非常常见的。很多话,但没有任何暗示你可能做错了什么。@HansPassant:如果我知道我可能做错了什么,我不会问这个问题,是吗?如果这能让你感觉更好的话,我可以发布上百行代码。但是,我没有找到任何一个源,有人按照我需要的方式进行操作。崩溃的性质是什么?对于重叠I/O,您必须使用
ReadFileEx
。@JonathanPotter:这不是真的
ReadFile()
还支持重叠I/O。
ReadFile()
ReadFileEx()
之间唯一的区别是
ReadFile()
支持同步和异步I/O,而
ReadFileEx()
只支持异步I/O。谢谢,我写的代码或多或少是匆忙创建的,因为我只是想让事情运行;)不幸的是,我们仍然在一些机器上遇到0xC0000005崩溃,主要是在对SerialPort::ReadData的调用中,有时是在ResetEvent调用中。在我见过的所有案例中,我们都来自WaitForMultipleObjec