我正在使用win-api为C ++中的COM端口编写一个类。现在,我通过连接的Rx和Tx引脚测试RS232的功能。
我遇到了一些奇怪的问题。我使用单独的线程从COM端口读取。在线程中,我使用SetCommMask
,WaitCommEvent
和WaitForSingleObject
等待char到达缓冲区。但是WaitForSingleObject
往往在没有实际接收任何字符的情况下退出。
我认为这是由于错误使用上述功能引起的,但是后来我发现,并不是每次都发生过早退出(第一次总是按预期的方式工作)。
第二步,线程进入等待状态并退出一段时间,然后继续前进到ReadFile
,在该线程中,它无限期等待,因为缓冲区为空,将不发送任何数据,并且不使用总超时。 / p>
已经建议我仅使用ReadFile
并仅处理获取的数据,但是我使用另一个线程来检查通信通道是否已断开连接,现在我需要区分等待数据和读取数据。
调用ClearCommError
来检查ReadFile
的输入缓冲区是不可行的,因为在这种情况下InQue
始终为0。因此我无法确定ReadFile
是否实际上是阅读或等待。
//following code runs in separate thread
DWORD dwEventMask1, dwEventMask2, LastError, Status;
OVERLAPPED Overlapped; HANDLE Serial_Port_Handle;
std::string stringBuffer("");
const size_t ReadBufferLength = 256;
char tempBuffer[ReadBufferLength];
GetCommMask(Serial_Port_Handle, &dwEventMask1);
if (dwEventMask1) // Before starting the thread I check the state of Input Buffer with GetCommError().
{ // If Buffer is not empty, CommMask is set to 0 signaling there is no need for waiting.
Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//wait for comm event
if (!WaitCommEvent(Serial_Port_Handle, &dwEventMask1, &Overlapped))
{
if ((LastError = GetLastError()) == ERROR_IO_PENDING)
{
Waiting = true; //signal bool for synchronization purposes
if ((Status = WaitForSingleObject(Overlapped.hEvent, INFINITE)) == WAIT_OBJECT_0)
{
GetCommMask(Serial_Port_Handle, &dwEventMask2);
Waiting = false;
CloseHandle(Overlapped.hEvent);
// I close handle and set all members of Overlapped struct to 0
}
if (dwEventMask2 !== dwEventMask1) // check if wait have not exited because of SetCommMast()
return;
}
}
}
do // this loop reads from input buffer until empty
{
Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//set up read overlapped operation
if (ReadFile(Serial_Port_Handle, tempBuffer, ReadBufferLength - 1, &NoBytesRead, &Overlapped)) //start read opperation
{ //Read operation done on 1 go
GetOverlappedResult(Serial_Port_Handle, &Overlapped, &NoBytesRead, FALSE); //get NoBytesRead
CloseHandle(Overlapped.hEvent)
stringBuffer.append(tempBuffer, (size_t) NoBytesRead); // save read data
}
else if ((LastError = GetLastError()) == ERROR_IO_PENDING) //operation not yet complete
{
GetOverlappedResult(Serial_Port_Handle, &Overlapped, &NoBytesRead, TRUE); // wait for completion
stringBuffer.append(tempBuffer, (size_t)NoBytesRead);
}
else
{
CloseHandle(Overlapped.hEvent)
return;
}
} while ((NoBytesRead == (ReadBufferLength - 1)) && NoBytesRead);
// Loop runs while tempBuffer's full capacity is used.
// I realize that since I don't use Total Timeout there is a possibility
// of the loop getting stuck. If you can suggest other solution than using
// Total Timeout or use GetCommError() to get input buffer state, please do so.
return;
此代码有些简化(检查返回值等)。
1)你们中有人有过这种行为吗?
2)我在代码中使用了OVERLAPPED操作。在操作退出之后,我总是使用CloseHandle
并重新初始化OVERLAPPED结构,然后再将其用于其他操作。这是正确的还是对结构进行了重置?
答案 0 :(得分:1)
从整体上看这是不好的逻辑。例如,存在以下问题。
您可能希望参考以下文章和源代码来重新设计程序:
Synchronization and Overlapped Input and Output
此外:
我没有注意到@Rita Han指出为WaitForSingleObject指定了文件句柄(不是事件句柄)。
最大的问题是。
但是,重新设计更好的情况没有改变。
@Rita Han的答案的源代码中没有对WaitCommEvent的描述,也没有对其进行重叠。同样,读取的数据大小在ReadFile中固定。
另一方面:
尽管它不在问题的源代码中出现,但WaitCommEvent / WaitForSingleObject可能会生成未在SetCommMask中指定的事件。
在WaitCommEvent等待完成时,请使用SetCommMask更改事件掩码。
Remarks - WaitCommEvent function
如果在重叠的WaitCommEvent操作正在进行时,进程尝试通过使用SetCommMask函数来更改设备句柄的事件掩码,则WaitCommEvent立即返回。 lpEvtMask参数指向的变量设置为零。
在WaitCommEvent等待完成时,请使用相同的Overlapped结构多次调用WaitCommEvent。
Synchronization and Overlapped Input and Output
在单个线程上执行多个同时重叠的操作时,调用线程必须为每个操作指定OVERLAPPED结构。每个OVERLAPPED结构都必须为不同的手动重置事件对象指定一个句柄。
线程不应重用事件,前提是该事件仅由该线程的重叠操作来发出信号。在与正在完成的重叠操作相同的线程上发出事件信号。在多个线程上使用相同的事件可能会导致竞争状态,在这种情况下,对于使用该事件的其他线程,其操作首先首先完成并过早完成的线程会正确地发出事件信号。
文档的描述如上所述,但是根据设备驱动程序/供应商的不同,稍后调用的WaitCommEvent会以错误结尾,而等待完成的WaitCommEvent是lpEvtMask返回零(如SetCommMask中一样)。
对于多个重叠的结构变量:
常见的编程知识是,将单个变量用于多种用途容易产生错误。
如果要使用异步和/或多线程设计,最好为ReadFile,WriteFile,WaitCommEvent准备至少三个Overlapped结构变量。
关于不管输入缓冲区的状态如何启动ReadFile:
这是关于在不获取设备驱动程序的输入缓冲区中接收到的数据大小的情况下,以256字节的固定长度调用ReadFile的情况。
实际上,即使所有数据都到达了,如果小于256个字节,它将始终被延迟直到发生256个字节的接收超时。
例如,循环一次读取一个字节,直到发生超时错误,这意味着接收到的数据结束(1字节读取超时不会有影响)。
或者,如前一篇文章所述,使用ClearCommError获取存储在设备驱动程序输入缓冲区中的数据大小,然后调用ReadFile指定该大小。
您正在解释的应用程序端缓冲区处理不会有问题。
关于调用SetCommMask时WaiCommEvent的行为:
这可能取决于您使用的设备驱动程序。
答案 1 :(得分:0)
但是,WaitForSingleObject往往没有实际退出 收到任何字符。
等待事件句柄,而不是串行设备句柄。
我知道了。以下是我的代码示例,您可以尝试:
DWORD errCode = 0;
BOOL result = false;
HANDLE serialDeviceHdl = CreateFile(L"COM8", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (!serialDeviceHdl)
{
errCode = GetLastError();
cout << "Open device failed. Error code: " << errCode << endl;
return 0;
}
OVERLAPPED overlappedForWrite = {};
overlappedForWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
DWORD writenSize = 0;
result = WriteFile(serialDeviceHdl, "hello", 5, &writenSize, &overlappedForWrite);
if (FALSE == result)
{
errCode = GetLastError();
if (ERROR_IO_PENDING == errCode)
{
cout << "Overlapped I/O operation is in progress." << endl;
}
else
{
cout << "Write to device failed. Error code: " << errCode << endl;
}
}
DWORD returnValue = WaitForSingleObject(overlappedForWrite.hEvent, INFINITE);
if (WAIT_OBJECT_0 == returnValue)
{
cout << "The state of the specified object is signaled." << endl;
}
else
{
cout << "Wait for single object failed. Error code: " << returnValue << endl;
}
CHAR readBuf[5];
DWORD readSize = 0;
OVERLAPPED overlappedForRead = {};
overlappedForRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
result = ReadFile(serialDeviceHdl, readBuf, 5, &readSize, &overlappedForRead);
if (FALSE == result)
{
errCode = GetLastError();
if (ERROR_IO_PENDING == errCode)
{
cout << "Overlapped I/O operation is in progress." << endl;
}
else
{
cout << "Write to device failed. Error code: " << errCode << endl;
}
}
returnValue = WaitForSingleObject(overlappedForRead.hEvent, INFINITE);
if (WAIT_OBJECT_0 == returnValue)
{
cout << "The state of the specified object is signaled." << endl;
}
else
{
cout << "Wait for single object failed. Error code: " << returnValue << endl;
}