Created
May 24, 2019 00:44
-
-
Save dmh2000/c776e6c5e27664008533c477427a2349 to your computer and use it in GitHub Desktop.
this demonstrates that ReadIOCompletion::GetOverlappedResult can return 0 bytes even though it appears it shouldn't
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
build with visual studio 2019 (2017/15 should work too), x64 debug build | |
*/ | |
#include <Windows.h> | |
#include <cstdio> | |
#include <cstdint> | |
#include <cstring> | |
#include <cassert> | |
struct Baton { | |
HANDLE fd = INVALID_HANDLE_VALUE; | |
char *buffer = nullptr; | |
DWORD bufferLength = 0; | |
DWORD bytesRead = 0; | |
DWORD bytesToRead = 0; | |
DWORD offset = 0; | |
void *hThread = nullptr; | |
bool complete = false; | |
}; | |
HANDLE open(const char* port) { | |
DCB dcb; | |
COMMTIMEOUTS tmo; | |
HANDLE fd; | |
BOOL b; | |
fd = CreateFile(port, | |
GENERIC_READ|GENERIC_WRITE, | |
0, | |
NULL, | |
OPEN_EXISTING, | |
FILE_FLAG_OVERLAPPED, | |
NULL | |
); | |
if (fd == INVALID_HANDLE_VALUE) { | |
return fd; | |
} | |
// setup DCB | |
memset(&dcb,0,sizeof(dcb)); | |
dcb.DCBlength = sizeof(dcb); | |
GetCommState(fd,&dcb); | |
dcb.Parity = NOPARITY; | |
dcb.ByteSize = 8; | |
dcb.StopBits = ONESTOPBIT; | |
dcb.fOutxDsrFlow = FALSE; | |
dcb.fOutxCtsFlow = FALSE; | |
dcb.fOutX = FALSE; | |
dcb.fInX = FALSE; | |
dcb.fRtsControl = RTS_CONTROL_DISABLE; | |
dcb.fBinary = TRUE; | |
dcb.BaudRate = 9600; | |
b = SetCommState(fd,&dcb); | |
if (b == FALSE) { | |
printf("%s:%d : %d\n",__FILE__,__LINE__,GetLastError()); | |
CloseHandle(fd); | |
return INVALID_HANDLE_VALUE; | |
} | |
// set commtimeouts to block | |
memset(&tmo,0,sizeof(tmo)); | |
tmo.ReadIntervalTimeout = 0; | |
tmo.ReadTotalTimeoutMultiplier = 0; | |
tmo.ReadTotalTimeoutConstant = 0; | |
tmo.WriteTotalTimeoutConstant = 0; | |
tmo.WriteTotalTimeoutMultiplier = 0; | |
b = SetCommTimeouts(fd,&tmo); | |
if (b == FALSE) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
CloseHandle(fd); | |
return INVALID_HANDLE_VALUE; | |
} | |
// Remove garbage data in RX/TX queues | |
PurgeComm(fd, PURGE_RXCLEAR); | |
PurgeComm(fd, PURGE_TXCLEAR); | |
return fd; | |
} | |
void __stdcall ReadIOCompletion(DWORD errorCode, DWORD bytesTransferred, OVERLAPPED* ov) { | |
Baton* baton = static_cast<Baton*>(ov->hEvent); | |
if (errorCode) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, errorCode); | |
baton->complete = true; | |
return; | |
} | |
if (!GetOverlappedResult(baton->fd, ov, &bytesTransferred, TRUE)) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
baton->complete = true; | |
return; | |
} | |
if (bytesTransferred) { | |
baton->bytesToRead -= bytesTransferred; | |
baton->bytesRead += bytesTransferred; | |
baton->offset += bytesTransferred; | |
} | |
// ==================================== | |
// BYTES TRANFERRED HERE CAN BE ZERO! | |
// ==================================== | |
if (bytesTransferred == 0) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
//baton->complete = true; | |
//return; | |
} | |
// ReadFileEx and GetOverlappedResult retrieved only 1 byte. Read any additional data in the input | |
// buffer. Set the timeout to MAXDWORD in order to disable timeouts, so the read operation will | |
// return immediately no matter how much data is available. | |
COMMTIMEOUTS commTimeouts = {}; | |
commTimeouts.ReadIntervalTimeout = MAXDWORD; | |
if (!SetCommTimeouts(baton->fd, &commTimeouts)) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
baton->complete = true; | |
return; | |
} | |
// Store additional data after whatever data has already been read. | |
char* offsetPtr = baton->buffer + baton->offset; | |
// ReadFile, unlike ReadFileEx, needs an event in the overlapped structure. | |
memset(ov, 0, sizeof(OVERLAPPED)); | |
ov->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
assert(ov->hEvent != nullptr); | |
if (!ReadFile(baton->fd, offsetPtr, baton->bytesToRead, &bytesTransferred, ov)) { | |
errorCode = GetLastError(); | |
if (errorCode != ERROR_IO_PENDING) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
baton->complete = true; | |
CloseHandle(ov->hEvent); | |
return; | |
} | |
if (!GetOverlappedResult(baton->fd, ov, &bytesTransferred, TRUE)) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
baton->complete = true; | |
CloseHandle(ov->hEvent); | |
return; | |
} | |
} | |
CloseHandle(ov->hEvent); | |
baton->bytesToRead -= bytesTransferred; | |
baton->bytesRead += bytesTransferred; | |
baton->complete = true; | |
} | |
DWORD __stdcall ReadThread(LPVOID param) { | |
Baton* baton = static_cast<Baton*>(param); | |
DWORD lastError; | |
BOOL b; | |
OVERLAPPED* ov = new OVERLAPPED; | |
memset(ov, 0, sizeof(OVERLAPPED)); | |
ov->hEvent = static_cast<void*>(baton); | |
while (!baton->complete) { | |
// Reset the read timeout to 0, so that it will block until more data arrives. | |
COMMTIMEOUTS commTimeouts = {}; | |
commTimeouts.ReadIntervalTimeout = 0; | |
if (!SetCommTimeouts(baton->fd, &commTimeouts)) { | |
lastError = GetLastError(); | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
break; | |
} | |
// ReadFileEx doesn't use overlapped's hEvent, so it is reserved for user data. | |
ov->hEvent = static_cast<HANDLE>(baton); | |
char* offsetPtr = baton->buffer + baton->offset; | |
// ReadFileEx requires calling GetLastError even upon success. Clear the error beforehand. | |
SetLastError(0); | |
// Only read 1 byte, so that the callback will be triggered once any data arrives. | |
b = ReadFileEx(baton->fd, offsetPtr, 1, ov, ReadIOCompletion); | |
if (b == FALSE) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
} | |
// Error codes when call is successful, such as ERROR_MORE_DATA. | |
lastError = GetLastError(); | |
if (lastError != ERROR_SUCCESS) { | |
break; | |
} | |
// IOCompletion routine is only called once this thread is in an alertable wait state. | |
SleepEx(INFINITE, TRUE); | |
} | |
delete ov; | |
// Signal the main thread to run the callback. | |
ExitThread(0); | |
} | |
DWORD read(HANDLE fd,char *buffer, DWORD len) | |
{ | |
Baton *baton = new Baton(); | |
baton->fd = fd; | |
baton->buffer = buffer; | |
baton->bytesToRead = len; | |
baton->bytesRead = 0; | |
baton->complete = false; | |
baton->offset = 0; | |
baton->hThread = CreateThread(NULL, 0, ReadThread, baton,0,nullptr); | |
// wait for asyn io to complete | |
while (!baton->complete) { | |
Sleep(100); | |
} | |
return baton->bytesRead; | |
} | |
int main() | |
{ | |
HANDLE fd; | |
fd = open("COM4"); | |
if (fd == INVALID_HANDLE_VALUE) { | |
printf("%s:%d : %d\n", __FILE__, __LINE__, GetLastError()); | |
return 1; | |
} | |
DWORD count; | |
char buffer[1024]; | |
int lf; | |
lf = 0; | |
for (;;) { | |
count = read(fd,buffer,sizeof(buffer)); | |
for (DWORD i = 0; i < count; ++i) { | |
printf("%02x ",(uint8_t)buffer[i]); | |
++lf; | |
if (lf > 30) { | |
lf = 0; | |
printf("\n"); | |
} | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment