I need a function to read data from a serial port or return if none came within a time interval. E.g. on GNU/Linux you could use poll()
orselect()
+read()
. Anything analogous in Windows?
Below is something I tried: it ought to work, but the function GetOverlappedResult() is either buggy or not well documented; instead of number of bytes read it reports nothing (not even zero, e.g. if I don't initialize the lpNumberOfBytesTransferred
argument, it just contains junk).
// NOTE: the `file` should be opened in non-blocking mode
long ReadData(HANDLE file, char* buf, long szbuf, int msTimeout){
OVERLAPPED ReadState = {0};
ReadState.hEvent = CreateEvent(0, true, false, "☠");
if (!ReadState.hEvent){
PrintLastErr();
return -1;
}
unsigned long BytesRead = 0; //number of bytes was read
if (!ReadFile(file, buf, szbuf, &BytesRead, &ReadState)){
// This creates a reading event. Below we wait for it to complete, and cancel on timeout
if (GetLastError() != ERROR_IO_PENDING){
PrintLastErr();
return -1;
}
// No, I can't use WaitForSingleObject(), it exits with disregard to whether
// reading is ongoing or no more data left (e.g. in case of a serial port).
while(1) {
std::this_thread::sleep_for(std::chrono::milliseconds( msTimeout ));
unsigned long ret;
puts("Getting result");
if (!GetOverlappedResult(file, &ReadState, &ret, false)
&& GetLastError() != ERROR_IO_INCOMPLETE){
PrintLastErr();
return -1;
}
printf("result is %lu\n",ret);
if(ret == BytesRead){
return BytesRead;
}
BytesRead = ret;
}
} else { //completed immediately
printf("Bytes read %i\n");
assert(BytesRead <= LONG_MAX);
return (long)BytesRead;
}
}
这是真的很难弄清楚; 但由于论坛和大量的实验,我终于找到了一种方法。 下面的代码:
/**
* Opens a file in overlapped mode.
* \returns HANDLE to a file, or 0 on fail
*/
HANDLE OpenFile(const char* FileName){
HANDLE file = CreateFile( FileName,
GENERIC_READ | GENERIC_WRITE,
0, //we're greedy(or just lazy)
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (file == INVALID_HANDLE_VALUE){
return 0;
}
if(!SetCommMask(file, EV_RXCHAR)){ // set a mask for incoming characters event.
return 0;
}
return file;
}
/**
* Waits for data to arrive
* \param file a file opened in overlapped mode
* \param msTimeout is a maximum time to wait
* \returns -1 on system error, 0 on success, and 1 if time out
*/
int WaitForData(HANDLE file, unsigned long msTimeout){
int ret;
unsigned long Occured;//returns the type of an occured event
OVERLAPPED FileEvent = {0};
FileEvent.hEvent = CreateEvent(0, true, false, 0);
do{
if(!WaitCommEvent(file, &Occured, &FileEvent)){
if(GetLastError() != ERROR_IO_PENDING){
ret = -1;
break;
}
}
switch(WaitForSingleObject(FileEvent.hEvent, msTimeout)){
case WAIT_OBJECT_0: //the requested event happened
ret = 0; //a success
break;
case WAIT_TIMEOUT://time out
ret = 1;
break;
default://error in WaitForSingleObject
ret = -1;
break;
}
break;
}while(0);
CloseHandle(FileEvent.hEvent);
return ret;
}
/**
* Reads data from a file
* \param file a file opened in overlapped mode
* \param buf a buf for data
* \param szbuf size of buffer
* \returns number of bytes read or -1 on fail
*/
unsigned long ReadData(HANDLE file, char* buf, unsigned long szbuf){
int ret;
unsigned long BytesRead = 0; //number of bytes was read
OVERLAPPED ReadState = {0};
do{
ReadState.hEvent = CreateEvent(0, true, false, 0);
if (!GetOverlappedResult(file, &ReadState, &BytesRead, false)){//get how many bytes incame
ret = ULONG_MAX;
break;
}
if (ReadFile( file,
buf,
(BytesRead<=szbuf) ? BytesRead : szbuf,
0,
&ReadState) == 0){
ret = ULONG_MAX;
break;
}
ret = BytesRead;
}while(0);
CloseHandle(ReadState.hEvent);
return ret;
}
那么它是怎样工作的?
- 打开与端口
FILE_FLAG_OVERLAPPED
标志 - 使用
SetCommMask()
设置EV_RXCHAR
,我们感兴趣的是输入数据的唯一事件。 - 在一个周期内等待数据的特定时间量,阅读如果有什么后果。
同时@ ScottMcP-MVP的答案是错的:有没有用的SetCommTimeouts()
在第一眼看到这个功能和COMMTIMEOUTS
看起来像你虽然想要什么,这驱使我困惑小时。 ☺
打开COM端口后调用SetCommTimeouts。 这台ReadFile的如果没有接收到数据的时间间隔后返回。 然后你只需调用ReadFile的。 无需重叠或事件或GetOverlappedResult。
文章来源: Win32 API: how to read the serial, or exit within a timeout if wasn't a data