I want to check if the WriteFile function is done writing to UART so that i can call ReadFile on the same ComDev without causing an Exception.
It seems the WriteFile function can return before writing is done.
BOOL WriteCommBlock(HANDLE * pComDev, char *pBuffer , int BytesToWrite)
{
while(fComPortInUse){}
fComPortInUse = 1;
BOOL bWriteStat = 0;
DWORD BytesWritten = 0;
COMSTAT ComStat = {0};
OVERLAPPED osWrite = {0,0,0};
if(WriteFile(*pComDev,pBuffer,BytesToWrite,&BytesWritten,&osWrite) == FALSE)
{
short Errorcode = GetLastError();
if( Errorcode != ERROR_IO_PENDING )
short breakpoint = 5; // Error
Sleep(1000); // complete write operation TBD
fComPortInUse = 0;
return (FALSE);
}
fComPortInUse = 0;
return (TRUE);
}
I used Sleep(1000) as an workaround, but how can i wait for an appropriate time?
You can create a Event, store it in your overlapped structure and wait for it to be signalled. Like this (untested):
BOOL WriteCommBlock(HANDLE * pComDev, char *pBuffer , int BytesToWrite)
{
while(fComPortInUse){}
fComPortInUse = 1;
BOOL bWriteStat = 0;
DWORD BytesWritten = 0;
COMSTAT ComStat = {0};
OVERLAPPED osWrite = {0,0,0};
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hEvent != NULL)
{
osWrite.hEvent = hEvent;
if(WriteFile(*pComDev,pBuffer,BytesToWrite,&BytesWritten,&osWrite) == FALSE)
{
short Errorcode = GetLastError();
if( Errorcode != ERROR_IO_PENDING )
short breakpoint = 5; // Error
WaitForSingleObject(hEvent, INFINITE);
fComPortInUse = 0;
return (FALSE);
}
CloseHandle(hEvent);
}
fComPortInUse = 0;
return (TRUE);
}
Note that depending on what else you are trying to do simply calling WaitForSingleObject() might not be the best idea. And neither might an INFINITE timeout.
Your problem is the incorrect use of the overlapped I/O, regardless to the UART or whatever underlying device.
The easiest (though not necessarily the most optimal) way to fix your code is to use an event to handle the I/O completion.
// ...
OVERLAPPED osWrite = {0,0,0};
osWrite.hEvent = CreateEvent(FALSE, NULL, NULL, FALSE);
if(WriteFile(*pComDev,pBuffer,BytesToWrite,&BytesWritten,&osWrite) == FALSE)
{
DWORD Errorcode = GetLastError();
// ensure it's ERROR_IO_PENDING
WaitForSingleObject(osWrite.hEvent, INFINITE);
}
CloseHandle(osWrite.hEvent);
Note however that the whole I/O is synchronous. It's handles by the OS in an asynchronous way, however your code doesn't go on until it's finished. If so, why do you use the overlapped I/O anyway?
One should use it to enable simultaneous processing of several I/Os (and other tasks) within the same thread. To do this correctly - you should allocate the OVERLAPPED structure on heap and use one of the available completion mechanisms: event, APC, completion port or etc. Your program flow logic should also be changed.
Since you didn't say that you need asynchronous I/O, you should try synchronous. It's easier. I think if you just pass a null pointer for the OVERLAPPED arg you get synchronous, blocking, I/O. Please see the example code I wrote in the "Windows C" section of this document:
http://www.pololu.com/docs/0J40/
Your Sleep(1000); is of no use, it will only execute after the writefile completes its operation.You have to wait till WriteFile is over.
if(WriteFile(*pComDev,pBuffer,BytesToWrite,&BytesWritten,&osWrite) == FALSE)
{}
You must be knowing that anything inside conditionals will only execute if the result is true.
And here the result is sent to the program after completion(whether complete or with error) of WriteFile routine.
OK, I missed the overlapped I/O OVL parameter in the read/write code, so It's just as well I only replied yesterday as a comment else I would be hammered with downvotes:(
The classic way of handling overlapped I/O is to have an _OVL struct as a data member of the buffer class that is issued in the overlapped read/write call. This makes it easy to have read and write calls loaded in at the same time, (or indeed, multiple read/write calls with separate buffer instances).
For COM posrts, I usually use an APC completion routine whose address is passed in the readFileEx/writeFileEx APIs. This leaves the hEvent field of the _OVL free to use to hold the instance pointer of the buffer so it's easy to cast it back inside the completion routine, (this means that each buffer class instance contains an _OVL memebr that contains an hEvent field that points to the buffer class instance - sounds a but weird, but works fine).
Related
I've been working on a program which dialog with an external device via a serial RS422 bus. The goal is to send commands to the device, which send an answer back.
So far, the code for sending a message look like this:
OVERLAPPED osWrite = {0};
void init()
{
// Create this write operation's OVERLAPPED structure's hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
std::cout << "Error osWrite.hEvent" << std::endl; // Error in communications; report it.
*hPort = CreateFile("\\\\.\\COM18", (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (*hPort == INVALID_HANDLE_VALUE) {
std::cout << "Invalid port com handle" << GetLastError() << std::endl;
return;
}
COMMTIMEOUTS commTimeout;
if (GetCommTimeouts(*hPort, &commTimeout)) {
commTimeout.ReadIntervalTimeout = 10;
commTimeout.ReadTotalTimeoutConstant = 10;
commTimeout.ReadTotalTimeoutMultiplier = 10;
commTimeout.WriteTotalTimeoutConstant = 10;
commTimeout.WriteTotalTimeoutMultiplier = 10;
} else
return;
if (!SetCommTimeouts(*hPort, &commTimeout)) {
std::cout << "Error comm timeout" << std::endl;
}
DCB dcb;
if (!GetCommState(*hPort, &dcb)) {
std::cout << "Invalid port com settings" << std::endl;
return;
}
dcb.BaudRate = CBR_115200;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
SetCommMask(*hPort, EV_RXCHAR);
SetCommState(*hPort, &dcb);
return;
}
DWORD serial_send(HANDLE *hPort, char *msg, int length) {
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;
PurgeComm(*hPort, PURGE_TXCLEAR);
ResetEvent(osWrite.hEvent);
// Issue write.
if (!WriteFile(*hPort, msg, length, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but isn't delayed. Report error and abort.
fRes = FALSE;
} else {
fRes = FALSE;
while (!fRes) {
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch (dwRes) {
// OVERLAPPED structure's event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(*hPort, &osWrite, &dwWritten, FALSE))
fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
break;
default:
// An error has occurred in WaitForSingleObject.
// This usually indicates a problem with the
// OVERLAPPED structure's event handle.
fRes = FALSE;
break;
}
}
}
} else {
// WriteFile completed immediately.
fRes = TRUE;
}
return dwWritten;
}
The last function can't return until the write operation is successful. The init() function load without error.
I've used a lot of code from here : https://learn.microsoft.com/en-us/previous-versions/ff802693(v=msdn.10)
Each message is 210 bytes long, and the serial port is running at 115200 bit/s, meaning that I should send a message every ~18.2ms. (210 bytes * 10 bits / 115200)
However, when I measure the time elapsed between 2 messages, I sometimes get a duration much inferior than the expected 18ms (it can go down to 11ms).
Is this a normal behavior for an asynchronous WriteFile + WaitForSingleObject?
What happens if I send another message just after 11ms, will it corrupt the previous message, or does it gets buffered?
I used std::chrono::high_resolution_clock::now() and std::chrono::duration<double, std::milli>(end - start).count() to get the duration of the frame, is it really accurate?
Since Windows is not a real-time OS and is a multi-process & multi-thread OS, time accuracy should not be guaranteed.
If the system is lightly loaded, most of it will work as intended, but not always.
Conversely, the completion of WriteFile() may be notified earlier than it actually is, depending on the hardware and device driver stack configuration.
For example, it may be considered that the process is completed at the time when the data is completely stored in the buffer of the device driver or when the last data is written to the FIFO buffer of the interface chip.
It is better to think that WriteFile() may be completed even if not all the data actually reaches the other party.
It is considered the same as writing file data to the hard disk. Completion of writing to a file on disk is done in the system buffer, and should be written to the actual media at a different time.
If the next serial_send() function is called before all the WriteFile data of the previous time has reached the other party due to bad conditions, there is a possibility that some of the previous transmission data will be discarded.
Because PurgeComm(*hPort, PURGE_TXCLEAR); is called at the beginning of the serial_send() function.
It's not as critical as specifying PURGE_TXABORT, but there is still the possibility of data being discarded with PURGE_TXCLEAR.
PurgeComm function
PURGE_TXABORT 0x0001 Terminates all outstanding overlapped write operations and returns immediately, even if the write operations have not been completed.
PURGE_TXCLEAR 0x0004 Clears the output buffer (if the device driver has one).
If a thread uses PurgeComm to flush an output buffer, the deleted characters are not transmitted. To empty the output buffer while ensuring that the contents are transmitted, call the FlushFileBuffers function (a synchronous operation).
The workaround is to simply not call PurgeComm().
If it is a serial port API, it may be possible to wait for the completion of transmission by specifying/detecting EV_TXEMPTY with SetCommMask()/WaitCommEvent(), but this will only be complicated.
SetCommMask function / WaitCommEvent function
EV_TXEMPTY 0x0004 The last character in the output buffer was sent.
Then your usage of WriteFile() + WaitForSingleObject() + GetOverlappedResult() will eventually work similar to calling WriteFile() synchronously.
Asynchronous operation is not always necessary, but it is better to consider in detail based on what kind of behavior your system requires.
I'm trying to write a simple class for operating a Serial Port on Windows, using standart windows library .
I need to check whether the input buffer is empty.
So far I've tried to use SetCommEvent, using EV_RXCHAR option, however this method doesn't work. The function seems to wait for arrival of new char. If I tried to send char, sleep for a second and the apply this, the function would not return - it keeps waiting.
bool isEmpty()
{
DWORD dwEventMask = 0;
DWORD Status = 0;
if (CheckAsyncRead())
return false;
if (!SetCommMask(hPort, EV_RXCHAR)) //wait for char receival
std::cout << "Error in creating Overlapped event" << std::endl;
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (WaitCommEvent(hPort, &dwEventMask, &osReader))
{
//close event handle
return false;
}
Status = WaitForSingleObject(osReader.hEvent, 10);
//I wait for 10 ms in case the function doesn't return immediately
//Close event handle
if (Status == WAIT_OBJECT_0)
{
return false;
}
else
return true;
}
I hoped the WaitCommEvent or WaitForSingleObject would return in case any chars were present in buffer but the does not happen if there is a longer pause between receival of a character and calling of Wait function.
You can use the ClearCommError function to find out the size of the data stored in the buffer.
As a result of calling, cbInQue of the COMSTAT structure to be notified has the size of the data stored in the input buffer.
You can use ReadFile() with a handle opened with CreateFile() with the FILE_FLAG_OVERLAPPED flag. If the ReadFile() function has nothing to return, it will return a last error ERROR_IO_PENDING which means that your buffer is currently empty.
As per subject I'm trying to develop a simple piped parent/child program.
Main purpose of this program is to keep the child process alive and use std::cin and std::cout to communicate between parent/child processes.
On Linux all of this works quite well.
On Windows I've been following the example here and there's one peculiar difference with Linux: one has to invoke
CloseHandle(g_hChildStd_IN_Wr)
To write to the child pipe and flush it. This has the side effect to close the pipe, thus terminating my in-connection to the child process.
I've also tried to use FlushFileBuffers but it doesn't work.
Any idea how can I flush the buffer without having to close the anonymous pipe?
Below sources of both Parent and Child processes.
If the code of the parent process is basically the one in the example above:
// IN_Wr_ is initialized as below with bInheritHandle=TRUE
::CreatePipe(&IN_Rd_, &IN_Wr_, &saAttr, 0);
// and
::SetHandleInformation(IN_Wr_, HANDLE_FLAG_INHERIT, 0)
// When I spawn the child process I do
STARTUPINFO siStartInfo = {0};
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = INVALID_HANDLE_VALUE;
siStartInfo.hStdOutput = OUT_Wr_;
siStartInfo.hStdInput = IN_Rd_;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
...
// then in order to write to std::cin
const DWORD reqSz = static_cast<DWORD>(std::strlen(request));
DWORD written = 0;
while(true) {
DWORD curWritten = 0;
if(!WriteFile(IN_Wr_, request + written, reqSz-written, &curWritten, NULL))
throw std::runtime_error("Error on WriteFile");
written += curWritten;
if(written == reqSz) {
// all written, done
break;
}
}
::FlushFileBuffers(IN_Wr_);
// only when I do this CloseHandle then the child process
// is able to read data
::CloseHandle(IN_Wr_);
this child code is a simple echo server, along the lines of:
buif[2048+1];
while(std::cin) {
std::cin.read(buf, 2048);
const auto rb = std::cin.gcount();
buf[rb] = '\0';
std::cout << buf << std::endl; // this does flush
}
Here's your problem:
std::cin.read(buf, 2048);
It's doing exactly what you've asked it to: waiting until it has read 2048 characters or reaches the end of file. You're not sending 2048 characters, so nothing happens until the server closes the pipe, which counts as the end of file in this context.
Instead, you should be using something like getline(s, 2048, '\0') which will stop reading when it sees a null character. (And, of course, you will need to modify the sender so that it writes that null character at the end of the string.)
Alternatively, you could use the native API: ReadFile has the semantics you seem to be wanting. Ideally you would use a message-mode pipe, which is designed precisely for this sort of use.
The article here might be helpful: https://support.microsoft.com/en-us/kb/190351. It has a section on flushing problems when printf is used to send data to the redirected pipe, which seems to be done in your case. The suggested solution is to use fflush(NULL) to flush the C run-time IO buffers.
It looks like the issue is a MSFT implementation of std::cin::read (and even fread(..., ..., ..., stdin)).
If instead of relying on:
// C++ API
while(std::cin) {
std::cin.read(buf, 2048);
...
// or also "C" API
int rb = 0;
while(0 < (rb = fread(buf, 2048, 1, stdin))) {
...
I do
// Low level Win32 "C" API
while(::ReadFile(hStdin, buf, 2048, &rb, 0)) {
...
// or also low level unix-like "C" API
int rb = 0;
while(0 < (rb = _read(0, buf, 2048))) {
...
The above example just works fine (funnily enough, the call to FlushFileBuffers is not even needed).
I'm writing a serial port software for Windows. To improve performance I'm trying to convert the routines to use asynchronous I/O. I have the code up and working fairly well, but I'm a semi-beginner at this, and I would like to improve the performance of the program further. During stress tests of the program (ie burst data to/from the port as fast as possible at high baudrate), the CPU load gets quite high.
If anyone out there has experience from asynchronous I/O and multi-threading in Windows, I'd be grateful if you could take a look at my program. I have two main concerns:
Is the asynchronous I/O implemented correctly? I found some fairly reliable source on the net suggesting that you can pass user data to the callback functions, by implementing your own OVERLAPPED struct with your own data at the end. This seems to be working just fine, but it does look a bit "hackish" to me. Also, the program's performance didn't improve all that much when I converted from synchronous/polled to asynchronous/callback, making me suspect I'm doing something wrong.
Is it sane to use STL std::deque for the FIFO data buffers? As the program is currently written, I only allow 1 byte of data to be received at a time, before it must be processed. Because I don't know how much data I will receive, it could be endless amounts. I assume this 1-byte-at-a-time will yield sluggish behaviour behind the lines of deque when it has to allocate data. And I don't trust deque to be thread-safe either (should I?).
If using STL deque isn't sane, are there any suggestions for a better data type to use? Static array-based circular ring buffer?
Any other feedback on the code is most welcome as well.
The serial routines are implemented so that I have a parent class called "Comport", which handles everything serial I/O related. From this class I inherit another class called "ThreadedComport", which is a multi-threaded version.
ThreadedComport class (relevant parts of it)
class ThreadedComport : public Comport
{
private:
HANDLE _hthread_port; /* thread handle */
HANDLE _hmutex_port; /* COM port access */
HANDLE _hmutex_send; /* send buffer access */
HANDLE _hmutex_rec; /* rec buffer access */
deque<uint8> _send_buf;
deque<uint8> _rec_buf;
uint16 _data_sent;
uint16 _data_received;
HANDLE _hevent_kill_thread;
HANDLE _hevent_open;
HANDLE _hevent_close;
HANDLE _hevent_write_done;
HANDLE _hevent_read_done;
HANDLE _hevent_ext_send; /* notifies external thread */
HANDLE _hevent_ext_receive; /* notifies external thread */
typedef struct
{
OVERLAPPED overlapped;
ThreadedComport* caller; /* add user data to struct */
} OVERLAPPED_overlap;
OVERLAPPED_overlap _send_overlapped;
OVERLAPPED_overlap _rec_overlapped;
uint8* _write_data;
uint8 _read_data;
DWORD _bytes_read;
static DWORD WINAPI _tranceiver_thread (LPVOID param);
void _send_data (void);
void _receive_data (void);
DWORD _wait_for_io (void);
static void WINAPI _send_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped);
static void WINAPI _receive_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped);
};
The main thread routine created through CreateThread():
DWORD WINAPI ThreadedComport::_tranceiver_thread (LPVOID param)
{
ThreadedComport* caller = (ThreadedComport*) param;
HANDLE handle_array [3] =
{
caller->_hevent_kill_thread, /* WAIT_OBJECT_0 */
caller->_hevent_open, /* WAIT_OBJECT_1 */
caller->_hevent_close /* WAIT_OBJECT_2 */
};
DWORD result;
do
{
/* wait for anything to happen */
result = WaitForMultipleObjects(3,
handle_array,
false, /* dont wait for all */
INFINITE);
if(result == WAIT_OBJECT_1 ) /* open? */
{
do /* while port is open, work */
{
caller->_send_data();
caller->_receive_data();
result = caller->_wait_for_io(); /* will wait for the same 3 as in handle_array above,
plus all read/write specific events */
} while (result != WAIT_OBJECT_0 && /* while not kill thread */
result != WAIT_OBJECT_2); /* while not close port */
}
else if(result == WAIT_OBJECT_2) /* close? */
{
; /* do nothing */
}
} while (result != WAIT_OBJECT_0); /* kill thread? */
return 0;
}
which in turn calls the following three functions:
void ThreadedComport::_send_data (void)
{
uint32 send_buf_size;
if(_send_buf.size() != 0) // anything to send?
{
WaitForSingleObject(_hmutex_port, INFINITE);
if(_is_open) // double-check port
{
bool result;
WaitForSingleObject(_hmutex_send, INFINITE);
_data_sent = 0;
send_buf_size = _send_buf.size();
if(send_buf_size > (uint32)_MAX_MESSAGE_LENGTH)
{
send_buf_size = _MAX_MESSAGE_LENGTH;
}
_write_data = new uint8 [send_buf_size];
for(uint32 i=0; i<send_buf_size; i++)
{
_write_data[i] = _send_buf.front();
_send_buf.pop_front();
}
_send_buf.clear();
ReleaseMutex(_hmutex_send);
result = WriteFileEx (_hcom, // handle to output file
(void*)_write_data, // pointer to input buffer
send_buf_size, // number of bytes to write
(LPOVERLAPPED)&_send_overlapped, // pointer to async. i/o data
(LPOVERLAPPED_COMPLETION_ROUTINE )&_send_callback);
SleepEx(INFINITE, true); // Allow callback to come
if(result == false)
{
// error handling here
}
} // if(_is_open)
ReleaseMutex(_hmutex_port);
}
else /* nothing to send */
{
SetEvent(_hevent_write_done); // Skip write
}
}
void ThreadedComport::_receive_data (void)
{
WaitForSingleObject(_hmutex_port, INFINITE);
if(_is_open)
{
BOOL result;
_bytes_read = 0;
result = ReadFileEx (_hcom, // handle to output file
(void*)&_read_data, // pointer to input buffer
1, // number of bytes to read
(OVERLAPPED*)&_rec_overlapped, // pointer to async. i/o data
(LPOVERLAPPED_COMPLETION_ROUTINE )&_receive_callback);
SleepEx(INFINITE, true); // Allow callback to come
if(result == FALSE)
{
DWORD last_error = GetLastError();
if(last_error == ERROR_OPERATION_ABORTED) // disconnected ?
{
close(); // close the port
}
}
}
ReleaseMutex(_hmutex_port);
}
DWORD ThreadedComport::_wait_for_io (void)
{
DWORD result;
bool is_write_done = false;
bool is_read_done = false;
HANDLE handle_array [5] =
{
_hevent_kill_thread,
_hevent_open,
_hevent_close,
_hevent_write_done,
_hevent_read_done
};
do /* COM port message pump running until sending / receiving is done */
{
result = WaitForMultipleObjects(5,
handle_array,
false, /* dont wait for all */
INFINITE);
if(result <= WAIT_OBJECT_2)
{
break; /* abort */
}
else if(result == WAIT_OBJECT_3) /* write done */
{
is_write_done = true;
SetEvent(_hevent_ext_send);
}
else if(result == WAIT_OBJECT_4) /* read done */
{
is_read_done = true;
if(_bytes_read > 0)
{
uint32 errors = 0;
WaitForSingleObject(_hmutex_rec, INFINITE);
_rec_buf.push_back((uint8)_read_data);
_data_received += _bytes_read;
while((uint16)_rec_buf.size() > _MAX_MESSAGE_LENGTH)
{
_rec_buf.pop_front();
}
ReleaseMutex(_hmutex_rec);
_bytes_read = 0;
ClearCommError(_hcom, &errors, NULL);
SetEvent(_hevent_ext_receive);
}
}
} while(!is_write_done || !is_read_done);
return result;
}
Asynchronous I/O callback functions:
void WINAPI ThreadedComport::_send_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
ThreadedComport* _this = ((OVERLAPPED_overlap*)lpOverlapped)->caller;
if(dwErrorCode == 0) // no errors
{
if(dwNumberOfBytesTransfered > 0)
{
_this->_data_sent = dwNumberOfBytesTransfered;
}
}
delete [] _this->_write_data; /* always clean this up */
SetEvent(lpOverlapped->hEvent);
}
void WINAPI ThreadedComport::_receive_callback (DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
if(dwErrorCode == 0) // no errors
{
if(dwNumberOfBytesTransfered > 0)
{
ThreadedComport* _this = ((OVERLAPPED_overlap*)lpOverlapped)->caller;
_this->_bytes_read = dwNumberOfBytesTransfered;
}
}
SetEvent(lpOverlapped->hEvent);
}
The first question is simple. The method is not hackish; you own the OVERLAPPED memory and everything that follows it. This is best described by Raymond Chen: http://blogs.msdn.com/b/oldnewthing/archive/2010/12/17/10106259.aspx
You would only expect a performance improvement if you've got better things to while waiting for the I/O to complete. If all you do is SleepEx, you'll only see CPU% go down. The clue is in the name "overlapped" - it allows you to overlap calculations and I/O.
std::deque<unsigned char> can handle FIFO data without big problems. It will probably recycle 4KB chunks (precise number determined by extensive profiling, all done for you).
[edit]
I've looked into your code a bit further, and it seems the code is needlessly complex. For starters, one of the main benefits of asynchronous I/O is that you don't need all that thread stuff. Threads allow you to use more cores, but you're dealing with a slow I/O device. Even a single core is sufficient, if it doesn't spend all its time waiting. And that's precisely what overlapped I/O is for. You just dedicate one thread to all I/O work for the port. Since it's the only thread, it doesn't need a mutex to access that port.
OTOH, you would want a mutex around the deque<uint8> objects since the producer/consumer threads aren't the same as the comport thread.
I don't see any reason for using asynchronous I/O in a project like this. Asynchronous I/O is good when you're handling a large number of sockets or have work to do while waiting for data, but as far as I can tell, you're only dealing with a single socket and not doing any work in between.
Also, just for the sake of knowledge, you would normally use an I/O completion port to handle your asynchronous I/O. I'm not sure if there are any situations where using an I/O completion port has a negative impact on performance.
But yes, your asynchronous I/O usage looks okay. Implementing your own OVERLAPPED struct does look like a hack, but it is correct; there's no other way to associate your own data with the completion.
Boost also has a circular buffer implementation, though I'm not sure if it's thread safe. None of the standard library containers are thread safe, though.
I think that your code has suboptimal design.
You are sharing too many data structures with too many threads, I guess. I think that you should put all handling of the serial device IO for one port into a single thread and put a synchronized command/data queue between the IO thread and all client threads. Have the IO thread watch out for commands/data in the queue.
You seem to be allocating and freeing some buffers for each sent event. Avoid that. If you keep all the IO in a single thread, you can reuse a single buffer. You are limiting the size of the message anyway, you can just pre-allocate a single big enough buffer.
Putting the bytes that you want to send into a std::deque is suboptimal. You have to serialize them into a continuous memory block for the WriteFile(). Instead, if you use some sort of commdand/data queue between one IO thread and other threads, you can have the client threads provide the continuous chunk of memory at once.
Reading 1 byte at a time seem silly, too. Unless it does not work for serial devices, you could provide large enough buffer to ReadFileEx(). It returns how many bytes it has actually managed to read. It should not block, AFAIK, unless of course I am wrong.
You are waiting for the overlapped IO to finish using the SleepEx() invocation. What is the point of the overlapped IO then if you are just ending up being synchronous?
I have a dll which includes a function called ReadPort that reads data from serial COM port, written in c/c++. This function is called within an extra thread from another WINAPI function using the _beginthreadex. When COM port has data to be read, the worker thread returns the data, ends normaly, the calling thread closes the worker's thread handle and the dll works fine.
However, if ReadPort is called without data pending on the COM port, when timeout occurs then WaitForSingleObject returns WAIT_TIMEOUT but the worker thread never ends. As a result, virtual memory grows at about 1 MB every time, physical memory grows some KBs and the application that calls the dll becomes unstable. I also tryied to use TerminateThread() but i got the same results.
I have to admit that although i have enough developing experience, i am not familiar with c/c++. I did a lot of research before posting but unfortunately i didn't manage to solve my problem.
Does anyone have a clue on how could i solve this problem? However, I really want to stick to this kind of solution. Also, i want to mention that i think i can't use any global variables to use some kind of extra events, because each dll's functions may be called many times for every COM port.
I post some parts of my code below:
The Worker Thread:
unsigned int __stdcall ReadPort(void* readstr){
DWORD dwError; int rres;DWORD dwCommModemStatus, dwBytesTransferred;
int ret;
char szBuff[64] = "";
ReadParams* params = (ReadParams*)readstr;
ret = SetCommMask(params->param2, EV_RXCHAR | EV_CTS | EV_DSR | EV_RLSD | EV_RING);
if (ret == 0)
{
_endthreadex(0);
return -1;
}
ret = WaitCommEvent(params->param2, &dwCommModemStatus, 0);
if (ret == 0)
{
_endthreadex(0);
return -2;
}
ret = SetCommMask(params->param2, EV_RXCHAR | EV_CTS | EV_DSR | EV_RLSD| EV_RING);
if (ret == 0)
{
_endthreadex(0);
return -3;
}
if (dwCommModemStatus & EV_RXCHAR||dwCommModemStatus & EV_RLSD)
{
rres = ReadFile(params->param2, szBuff, 64, &dwBytesTransferred,NULL);
if (rres == 0)
{
switch (dwError = GetLastError())
{
case ERROR_HANDLE_EOF:
_endthreadex(0);
return -4;
}
_endthreadex(0);
return -5;
}
else
{
strcpy(params->param1,szBuff);
_endthreadex(0);
return 0;
}
}
else
{
_endthreadex(0);
return 0;
}
_endthreadex(0);
return 0;}
The Calling Thread:
int WINAPI StartReadThread(HANDLE porthandle, HWND windowhandle){
HANDLE hThread;
unsigned threadID;
ReadParams readstr;
DWORD ret, ret2;
readstr.param2 = porthandle;
hThread = (HANDLE)_beginthreadex( NULL, 0, ReadPort, &readstr, 0, &threadID );
ret = WaitForSingleObject(hThread, 500);
if (ret == WAIT_OBJECT_0)
{
CloseHandle(hThread);
if (readstr.param1 != NULL)
// Send message to GUI
return 0;
}
else if (ret == WAIT_TIMEOUT)
{
ret2 = CloseHandle(hThread);
return -1;
}
else
{
ret2 = CloseHandle(hThread);
if (ret2 == 0)
return -2;
}}
Thank you in advance,
Sna.
Don't use WaitCommEvent. You can call ReadFile even when there is no data waiting.
Use SetCommTimeouts to make ReadFile itself timeout, instead of building a timeout on the inter-thread communications.
Change the delay in the WaitForSingleObject call to 5000 or 10000 and I bet your problem frequency goes way down.
Edwin's answer is also valid. The spawned thread does not die because you closed the thread handle.
There is no guarantee that the ReadPort thread has even started by the time you are timing out. Windows takes a LONG time to start a thread.
Here are some suggestions:
You never check the return value of beginthreadex. How do you know the thread started?
Use whatever synchronization method with which you are comfortable to sync the ReadPort thread startup with StartReadThread. It could be as simple as an integer flag that ReadPort sets to 1 when its ready to work. Then the main thread can start its true waiting at that point. Otherwise you'll never know short of using a debugger what's happening between the 2 threads. Do not time out from the call to WaitForSingleObject in StartReadThread until your sync method indicates that ReadPort is working.
You should not use strcpy to copy the bytes received from the serial port with ReadFile. ReadFile tells you how many bytes it read. Use that value and memcpy to fill the buffer.
Look here and here for info on how to have ReadFile time out so your reads are not indefinite. Blocking forever on Windows is a recipe for disaster as it can cause zombie processes you cannot kill, among other problems.
You communicate no status to StartReadThread about what happened in the ReadPort thread. How do you know how many bytes ReadPort placed into szBuff? To get the theads exit code, use GetExitCodeThread. Documented here. Note that you cannot use GetExitCodeThread if you've closed the thread handle.
In your calling thread after a timeout you close the threadhandle. This will only stop you from using the handle. The worker thread however is still running. You should use a loop which waits again.