我在跨平台应用程序(具有 Linux 嵌入式和实际嵌入式目标)中的串行端口存在一些问题,它也适用于 Windows 以使开发更容易。这是关于 Windows 实现的。
因此,串行协议的实现针对的是 OS 和非 OS 系统的混合,我不会触及实现本身。我想让它与现有的实现兼容。如果在合理时间内失败,我将创建一个单独的线程用于串行读取。
好的,基本上这个实现会打开串口,在我们的 IO 系统(epoll
在 Linux 和WaitForMultipleObjects
Windows 上使用)中注册文件描述符,然后,基本上,只是等待所有句柄并执行任何需要的操作。因此,当句柄发出读取信号时,我们希望从串行端口读取。不幸的是,在 Windows 上,您无法指定是等待读取还是写入,所以我想我会使用以下解决方案:
CreateFile
和FILE_FLAG_OVERLAPPED
SetCommMask
和EV_RXCHAR
- 创建
OVERLAPPED
具有手动重置事件的结构 WaitCommEvent
使用所述结构调用OVERLAPPED
,通常返回ERROR_IO_PENDING
这就是基本设置。我注册了事件句柄而不是文件句柄来等待。当手柄发出信号时,我执行以下操作:
ReadFile
- 如果成功,再次
ResetEvent
调用WaitCommEvent
但是,如果您指定FILE_FLAG_OVERLAPPED
,您似乎也必须使用重叠 IO 进行读取和写入。所以我想无论何时ReadFile
或WriteFile
返回,我都会用andERROR_IO_PENDING
等待 IO 。不过,我似乎并没有参与其中。它似乎基本上可以工作,但有时它会在其中一个调用上崩溃,好像重叠仍然处于活动状态(尽管我猜它仍然不应该崩溃)。WaitForSingleObject
GetOverlappedResult
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.
}
}
通过一个相当长的链,线程然后调用WaitForMultipleObjects
m_ ,它与结构的成员waitHandle
相同。这是在一个循环中完成的,并且列表中还有其他几个句柄,这就是为什么这与您有一个线程专门从串行端口读取的典型解决方案不同的原因。基本上,我无法控制循环,这就是为什么我尝试在正确的时间执行(within )。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;
}
看来它现在确实有效。再也没有崩溃了,至少我无法重现它们。所以它现在有效,我只是问我所做的是否理智,或者我是否应该以不同的方式做事。