I'm having some problems with serial ports in a cross-platform application (with Linux embedded and actual embedded targets), which also works on Windows to make development easier. This is about the Windows implementation.
The implementation of the serial protocol is, therefore, targetted at a mixture of OS- and non-OS systems and I won't touch the implementation itself. I'd like to make it compatible with the existing implementation. If that fails within reasonable time, I'll just make a separate thread for serial reading.
OK, basically the implementation opens the serial port, registers the file descriptor in our IO system (which uses epoll on Linux and WaitForMultipleObjects on Windows) and then, basically, just waits for all handles and does whatever required. So we want to read from the serial port when the handle is signaled for reading. Unfortunately on Windows, you can't specify if you're waiting for read or write, so I thought I'd use the following solution:
CreateFile with FILE_FLAG_OVERLAPPED
SetCommMask with EV_RXCHAR
Create an OVERLAPPED structure with a manual reset event
Call WaitCommEvent with said OVERLAPPED structure, which usually returns ERROR_IO_PENDING
That's the basic setup. I register the event handle instead of the file handle to wait on. When the handle is signalled, I do the following:
ReadFile
If successful, ResetEvent and call WaitCommEvent again
It seems, however, that if you specify FILE_FLAG_OVERLAPPED, you must use overlapped IO also for reading and writing. So I thought that whenever ReadFile or WriteFile return ERROR_IO_PENDING, I'll just wait for the IO with WaitForSingleObject and GetOverlappedResult. It seems that I don't get into that though. It seems to work basically, but sometimes it crashes on one of the ResetEvent calls, as if the overlapped was still active (though I guess it still shouldn't crash).
So, the actual question. Can this be done as I want it? Is there a problem with the approach in general, or should it work? Or is using yet another thread the only good solution? The communication is already in a separate thread, so it would be at least three threads then.
I'll try to post as much code as needed, though it is reduced from the actual code which contains a lot of things not directly related to serial reading.
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;
}
The constructor is called in a separate thread, which later calls 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.
}
}
Via a rather long chain, the thread then calls WaitForMultipleObjects on m_waitHandle, which is the same as the hEvent member of the OVERLAPPED structure. This is done in a loop, and there are several other handles in the list, that's why this is different from the typical solution where you have a thread exclusively reading from the serial port. I have, basically, no control about the loop, that's why I try to do the WaitCommEvent (within InitWait) at just the right time.
When the handle is signaled, the ReadData method is called by the thread:
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;
}
}
The write is done as follows, without syncing:
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;
}
It seems that it does work now. There are no crashes anymore, at least I can't reproduce them. So as it works now, I'm just asking if what I do is sane, or if I should do things differently.
Offhand, I don't see any errors in the code you have shown, but I would like to suggest alternative code to clean up your error handling in ReadData() and WriteData() in general:
int SerialPort::ReadData(void *buffer, int size)
{
if (fd == INVALID_HANDLE_VALUE)
return 0;
OVERLAPPED ovRead = {0};
ovRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ovRead.hEvent)
return -1;
DWORD dwBytesRead;
if (!ReadFile(fd, buffer, size, &dwBytesRead, &ovRead))
{
if (GetLastError() != ERROR_IO_PENDING)
{
CloseHandle(ovRead.hEvent);
return -1;
}
if (!GetOverlappedResult(fd, &ovRead, &dwBytesRead, TRUE))
{
CloseHandle(ovRead.hEvent);
return -1;
}
}
if (WaitForSingleObject(m_waitHandle, 0) == WAIT_OBJECT_0)
{
ResetEvent(m_waitHandle);
InitWait();
}
CloseHandle(ovRead.hEvent);
return dwBytesRead;
}
int SerialPort::WriteData(const void *buffer, int size)
{
if (fd == INVALID_HANDLE_VALUE)
return 0;
OVERLAPPED ovWrite = {0};
ovWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ovWrite.hEvent)
return -1;
DWORD dwBytesWritten;
if (!WriteFile(fd, buffer, size, &dwBytesWritten, &ovWrite))
{
if (GetLastError() != ERROR_IO_PENDING)
{
CloseHandle(ovWrite.hEvent);
return -1;
}
if (!GetOverlappedResult(fd, &ovWrite, &dwBytesWritten, TRUE))
{
CloseHandle(ovWrite.hEvent);
return -1;
}
}
CloseHandle(ovWrite.hEvent);
return dwBytesWritten;
}
Related
In order to write to HD at max. performance I'm using overlapped I/O.
It works.
Upon acquiring 4MB of data (from sensor) I'm writing it to disk.
Then, upon getting the next 4MB I first ask if the previous writing completed.
How can I know what is the optimal block size (4MB ?) that is best for my disk ?
// AsyncFile.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "AsyncFile.h"
/****************************************************************************/
CAsyncFile::CAsyncFile()
{
}
/****************************************************************************/
CAsyncFile::~CAsyncFile()
{
}
/****************************************************************************/
int CAsyncFile::OpenFile(char *pcFileName,
bool bAsync, // Whether async read/write is required
bool bWrite) // True is file is used for writing to
{
DWORD dwAsyncMask = bAsync ? (FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING) : 0;
DWORD dwCreation = bWrite ? CREATE_ALWAYS : OPEN_EXISTING;
DWORD dwAccess = bWrite ? GENERIC_WRITE : GENERIC_READ;
DWORD dwShareMode = bWrite ? FILE_SHARE_READ : FILE_SHARE_WRITE;
if (strlen(pcFileName) < sizeof(m_cFileName))
strcpy_s(m_cFileName, 256, pcFileName);
else
m_cFileName[0] = 0; // NULL (error - file name is too long)
// Calling openFile() sets a valid value to the file handle
m_hFileHandle = INVALID_HANDLE_VALUE;
// Auto reset (manual reset=false), init state = false, no name
m_hIoCompleted = CreateEvent(NULL, FALSE, FALSE, NULL);
// Init OVERLAPPED structure, for async read
m_tOverlapped.Offset = 0;
m_tOverlapped.OffsetHigh = 0;
m_tOverlapped.hEvent = m_hIoCompleted;
m_Event = m_tOverlapped.hEvent;
if (m_hFileHandle != INVALID_HANDLE_VALUE)
{
// File is already openned; check open mode
if ((bAsync == m_bAsync) && (bWrite == m_bWrite))
return (ASYNCFILE_OK);
// File is already openned, but in other mode; Should close file
// before using it again
return ASYNCFILE_FILE_IS_NOT_IN_WRITE_MODE;
}
m_hFileHandle =
CreateFile((LPCTSTR)m_cFileName,
dwAccess, // Open for read or write
dwShareMode, //
NULL, // No SECURITY_ATTRBUTES
dwCreation, // Open exisiting file (if read) \ create new (if write)
dwAsyncMask, // For asynchronous operations, for maximum asynchronous performence
0);
if (m_hFileHandle == INVALID_HANDLE_VALUE)
{
DWORD dwError = GetLastError();
return ASYNCFILE_FAILED_TO_OPEN_FILE;
}
//In case file opened for reading, get its size
if (bWrite == false)
{
GetFileSizeEx(m_hFileHandle, &m_FileSize);
}
// Save open mode
m_bAsync = bAsync;
m_bWrite = bWrite;
return ASYNCFILE_OK;
}
/****************************************************************************/
int CAsyncFile::CloseFile()
{
//BOOL Status;
if (!CloseHandle(m_hFileHandle))
return ASYNCFILE_FAILED_TO_CLOSE_FILE;
if (!CloseHandle(m_hIoCompleted))
return ASYNCFILE_FAILED_TO_CLOSE_FILE;
return ASYNCFILE_OK;
}
/****************************************************************************/
int CAsyncFile::StartAsyncRead(void* pBuffer,
DWORD dwReadSize,
bool* pbEof)
{
*pbEof = false; // By default, EOF is false
int iError;
if (m_hFileHandle == INVALID_HANDLE_VALUE)
return (false);
if (!ReadFile(m_hFileHandle,
pBuffer,
dwReadSize,
NULL, // actual bytes read is not valid now
&m_tOverlapped))
{
if ((iError = GetLastError()) == ERROR_HANDLE_EOF)
{
*pbEof = true;
return ASYNCFILE_OK;
}
else if (!(m_bAsync && (iError == ERROR_IO_PENDING)))
{
return ASYNCFILE_START_READ_FAILED;
}
}
return ASYNCFILE_OK;
}
/****************************************************************************/
int CAsyncFile::WaitAsyncOperationEnd(DWORD* pdwActualBytesTransferred)
{
if (m_hFileHandle == INVALID_HANDLE_VALUE)
return ASYNCFILE_WAIT_FOR_COMPLETION_FAILED;
// Wait for read operation to complete
if (!GetOverlappedResult(m_hFileHandle,
&m_tOverlapped,
pdwActualBytesTransferred,
true))
return ASYNCFILE_WAIT_FOR_COMPLETION_FAILED;
return ASYNCFILE_OK;
}
/****************************************************************************/
int CAsyncFile::StartAsyncWrite(void* pSrcBuf,
DWORD dwSize) // In bytes
{
int iError;
if (!WriteFile(m_hFileHandle,
pSrcBuf,
dwSize,
NULL, // actual bytes written is not valid now
&m_tOverlapped))
{
iError = GetLastError();
if (iError != ERROR_IO_PENDING)
return ASYNCFILE_START_WRITE_FAILED;
}
return ASYNCFILE_OK;
}
/****************************************************************************/
void CAsyncFile::SetFilePosition(UINT64 Position)
{
m_tOverlapped.Offset = Position & 0xFFFFFFFF;
m_tOverlapped.OffsetHigh = Position >> 32;
}
/****************************************************************************/
UINT64 CAsyncFile::GetFilePosition()
{
UINT64 Position;
Position = (m_tOverlapped.Offset) | ((UINT64)m_tOverlapped.OffsetHigh << 32);
return (Position);
}
/****************************************************************************/
UINT64 CAsyncFile::GetFileSize()
{
return (m_FileSize.QuadPart);
}
Well it depends on the average size of your files as well. If your file sizes are constantly ranging near 7 to 8 MB, then there would be some benefit in increasing the buffer size to 8192KB. How old is the drive? How often have you used it, many other factors come into play. We are able to learn more about this topic from an excellent piece of software called FastCopy. Hope this helps.
I'm facing an issue doing a select() call waiting on a socket + pipe.
I know there are already some topics on that but I have read lots of things and their opposite and I can't figure out what is the best solution for my problem.
The best for me would be to use WaitForMultipleObjects() listening on these two objects but when I try to call it only on the WSAEvent object, it fails and last error catch is code 6 (Invalid Handle).
WSAEVENT sockEvent = WSACreateEvent();
sockEvent = WSAEventSelect(fd, sockEvent, FD_WRITE);
HANDLE *pHandles = &sockEvent;
DWORD dwEvent = WaitForMultipleObjects(1, pHandles, FALSE, amqp_time_ms_until(deadline));
switch (dwEvent)
{
// ghEvents[0] was signaled
case WAIT_OBJECT_0 + 0:
// TODO: Perform tasks required by this event
return AMQP_STATUS_OK;
// ghEvents[1] was signaled
case WAIT_OBJECT_0 + 1:
// TODO: Perform tasks required by this event
return AMQP_STATUS_POLL_EXTERNAL_WAKE;
case WAIT_TIMEOUT:
return AMQP_STATUS_TIMEOUT;
// Return value is invalid.
default:
return AMQP_STATUS_SOCKET_ERROR;
}
So WaitForMultipleObjects doesn't seems to Work with WinSocks events, however I have already seen some examples on the net working with it.
And the of WSACreateEvent documentation (https://msdn.microsoft.com/en-us/library/windows/desktop/ms741561%28v=vs.85%29.aspx) says this :
Windows Sockets 2 event objects are system objects in Windows
environments. Therefore, if a Windows application wants to use an
auto-reset event rather than a manual-reset event, the application can
call the CreateEvent function directly.
This doesn't mean that WSAEvent are based on regular windows events ? If it's the case why it doesn't work with WaitForMultipleObjects ? The doc says it can handle regular events.
Thanks for helping.
This is your problem:
sockEvent = WSAEventSelect(fd, sockEvent, FD_WRITE);
You're overwriting the event handle! (As documented, the return value for WSAEventSelect is either 0 or SOCKET_ERROR. It is not a new event handle.)
Try something like
if (WSAEventSelect(fd, sockEvent, FD_WRITE) != 0) return SOCKET_ERROR;
Looking at the declaration of WSAEVENT revealed that WSAEVENT is simply an alias for HANDLE. This explains the note of the WSACreateEvent documentation you added to your post. So WSACreateEvent simply creates a manual reset event by calling CreateEvent(..., TRUE, FALSE, ...);.
Therefore an event returned by WSACreateEvent has to work along with WaitForMultipleObjects(..).
According to the code you've posted I cannot see any reason why WaitForMultipleObjects(..) should return "invalid handle" when supplied with an event returned by WSACreateEvent...
It may be though that pipes do not work with WaitForMultipleObjects(..). I remember having problems with that a long time ago but I cannot remember the details right now. But maybe it is another place to start digging...
Here is the code of my little test application which creates two threads (one event thread signalling a normal event and a simple TCP/IP server sending data). In the main loop a connection to the server is established and signalled events are processed.
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#pragma comment(lib, "Ws2_32.lib");
#define SERVER_PORT 5000
HANDLE hSomeEvent;
HANDLE hSocketEvent;
DWORD WINAPI eventThread(LPVOID pData)
{
while (1)
{
SleepEx(2250, FALSE);
SetEvent(hSomeEvent);
}
return (0);
}
DWORD WINAPI serverThread(LPVOID pData)
{
SOCKET listener;
struct sockaddr_in sockaddr;
int size;
SOCKET client;
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == INVALID_SOCKET)
{
printf("Could not create socket : %d" , WSAGetLastError());
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(SERVER_PORT);
if (bind(listener, (struct sockaddr *)&sockaddr , sizeof(sockaddr)) == SOCKET_ERROR)
{
printf("Bind failed with error code : %d" , WSAGetLastError());
}
listen(listener, 1);
while (listener)
{
size = sizeof(struct sockaddr_in);
client = accept(listener, (struct sockaddr *)&sockaddr, &size);
printf("client connected\n");
while (client != INVALID_SOCKET)
{
SleepEx(5000, FALSE);
if (send(client, "hello\0", 6, 0) != 6)
{
closesocket(client);
shutdown(client, 2);
client = INVALID_SOCKET;
}
}
SetEvent(hSomeEvent);
}
return (0);
}
int main()
{
WSADATA wsaData;
HANDLE events[2];
DWORD result;
SOCKET s;
struct hostent *hp;
struct sockaddr_in sockaddr;
int len;
char buff[1024 * 16];
HANDLE *evtPtr;
WSAStartup(MAKEWORD(2, 2), &wsaData);
hSocketEvent = WSACreateEvent();
//hSocketEvent = CreateEvent(NULL, FALSE, FALSE, "socket_event");
hSomeEvent = CreateEvent(NULL, FALSE, FALSE, "some_event");
CreateThread(NULL, 0, eventThread, NULL, 0, &result);
CreateThread(NULL, 0, serverThread, NULL, 0, &result);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
printf("Could not create socket : %d" , WSAGetLastError());
}
hp = gethostbyname("127.0.0.1");
sockaddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(SERVER_PORT);
if (connect(s, (struct sockaddr*)&sockaddr, sizeof(sockaddr)))
{
closesocket(s);
printf("Could not connect socket : %d" , WSAGetLastError());
}
WSAEventSelect(s, hSocketEvent, FD_READ);
do
{
//events[0] = hSocketEvent;
//events[1] = hSomeEvent;
//result = WaitForMultipleObjects(2, events, FALSE, 1000);
evtPtr = &hSocketEvent;
result = WaitForMultipleObjects(1, evtPtr, FALSE, 1000);
switch (result)
{
case WAIT_OBJECT_0 + 0:
printf("hSocketEvent is signalled!\n");
len = recv(s, buff, sizeof(buff), 0);
printf(" %d bytes received\n", len);
WSAResetEvent(hSocketEvent);
break;
case WAIT_OBJECT_0 + 1:
printf("hSomeEvent is signalled!\n");
break;
case WAIT_TIMEOUT:
printf("timeout\n");
break;
default:
printf("error = %d\n", GetLastError());
break;
}
}
while (1);
printf("\n\nend.");
getch();
return (0);
}
Note that if you use WSACreateEvent you have to manually reset the event after readinng the data (otherwise WaitForMultipleObjects(..) will go nuts).
I have a application running on windows, which will send data over serial port.
Here is the code:
m_hCommPort= ::CreateFile(L"\\\\.\\COM3",
GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING,0,0);
if(m_hCommPort == INVALID_HANDLE_VALUE)
{
printf("COM error: %d\n", GetLastError());
}
config.DCBlength = sizeof(config);
if((GetCommState(m_hCommPort, &config) == 0))
{
CloseHandle(m_hCommPort);
printf("Get configuration port has a problem.\n");
return FALSE;
}
config.BaudRate = 9600;
config.StopBits = ONESTOPBIT;
config.Parity = PARITY_NONE;
config.ByteSize = DATABITS_8;
config.fDtrControl = 0;
config.fRtsControl = 0;
if (!SetCommState(m_hCommPort, &config))
{
CloseHandle(m_hCommPort);
printf( "Failed to Set Comm State Reason: %d\n",GetLastError());
return E_FAIL;
}
Here is the code for Send only (Working) (continuously sending )
while(1)
{
Sleep(5000);
int isWritten = WriteFile(m_hCommPort, txData, 9/*(DWORD)sizeof(txData)*/, &dwBytesWritten, NULL);
printf("isWritten: %d, dwBytesWritten: %d \n", isWritten, dwBytesWritten);
}
After this I have added code for Receive data too, then send is NOT WORKING. I mean not able to send data over UART. WriteFile() seems not executed, its stuck.
Here I have added a thread to receive data, is thread causing the problem ? or do I need to do something else ?
void ReceiverThread(void *param)
{
DWORD dwRead=0;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
printf("Error creating overlapped event; abort.\n");
while(1)
{
if (!ReadFile(m_hCommPort, &Byte, 1, &dwRead, &osReader)) {
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
printf("Error in communications; report it.\n");
else
fWaitingOnRead = TRUE;
}
else {
rxData[rxHeadIndex++]= Byte;
rxHeadIndex = (rxHeadIndex) & QUEUE_MASK;
}
}
}
SetCommMask (m_hCommPort, EV_RXCHAR/ | EV_ERR); //receive character event
_beginthread(ReceiverThread,0,NULL);
while(1)
{
Sleep(5000);
int isWritten = WriteFile(m_hCommPort, txData, 9/*(DWORD)sizeof(txData)*/, &dwBytesWritten, NULL);
printf("isWritten: %d, dwBytesWritten: %d \n", isWritten, dwBytesWritten);
}
Thanks in advance.
Ashok
I've encountered a similar problem a while ago.
I found out that WriteFile(..) blocks if a ReadFile(..) is currently in progress for a serial port. So that's a problem if ReadFile(..) blocks if there is no data to be read.
I've solved the problem by checking if there is data available within the serial buffer to be read by using the function ClearCommError(..). This ensures that ReadFile(..) can read something immediately and does not unnecessarily block the device. Try changing your ReceiverThread into something like this:
void ReceiverThread(void *param)
{
DWORD dwRead=0;
COMSTAT comStat;
char buffer[1024];
while (m_hCommPort != INVALID_HANDLE_VALUE)
{
if ((ClearCommError(m_hCommPort, NULL, &comStat) != FALSE) && (comStat.cbInQue > 0))
{
/* todo: ensure buffer is big enough to contain comStat.cbInQue bytes! */
if (ReadFile(m_hCommPort, buffer, comStat.cbInQue, &dwRead, NULL) != FALSE)
{
/* do something with data in buffer */
}
}
/* avoid busy-wait */
if (comStat.cbInQue == 0)
{
SleepEx(1, FALSE);
}
}
}
This way ReadFile(..) is only called if data is available and meanwhile WriteFile(..) can send data without being blocked.
Unfortunately I've not been able to make ClearCommError(..) blocking so I used the SleepEx(1, FALSE); work-around to avoid a busy-wait and therefore prefenting the ReceiverThread to eat up the CPU.
config.fDtrControl = 0;
config.fRtsControl = 0;
These settings turn the DTR and RTS handshake lines off. Most serial devices pay attention to these signals. They won't send anything when your DTR signal is off, assuming that the machine is not powered up. And won't send anything when your RTS signal is off, assuming that the machine is not ready to receive any data.
So what you observed is entirely normal.
Since the device appears to be "normal" and does pay attention to the handshake lines, you'll want to configure the DCB to let the device driver automatically control these signals. Fix:
config.fDtrControl = DTR_CONTROL_ENABLE;
config.fRtsControl = RTS_CONTROL_HANDSHAKE;
Also the default for terminal emulators like Putty and HyperTerminal. Use such a program first to ensure that the wiring and device are functional. If you can't get any device data to show up in such a program then it won't work with your program either. If this all checks out then also set the fDsrSensitivity, fOutxCtsFlow and fOutxDsrFlow properties to TRUE so that you will, in turn, pay attention to the handshake signals of the device.
I've managed to use ReadDirectoryChangesW synchronously, but when I attempt to use completion ports, ReadDirectoryChangesW always returns ERROR_INVALID_PARAMETER. I guess there should be some obvious error in my code, but I cannot figure it.
My code is based on How to use ReadDirectoryChangesW() method with completion routine?
const wchar_t *directory = L"X:\\X";
HANDLE h = CreateFile(
directory,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL);
if (h==INVALID_HANDLE_VALUE) return;
HANDLE p = CreateIoCompletionPort(h,0,0,1);
if (p==NULL) {CloseHandle(h); return;}
DWORD *buffer =new DWORD[4096];
DWORD bytesReturned;
DWORD notifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE;
while (true) {
OVERLAPPED overlapped;
memset(&overlapped,0,sizeof(overlapped));
BOOL success = ReadDirectoryChangesW(h,
&buffer[0],
4096*sizeof(DWORD),
FALSE, notifyFilter,
NULL, //&bytesReturned,
&overlapped,myFileIOCompletionRoutine);
if (!success) {
//always ERROR_INVALID_PARAMETER
CloseHandle(h);
CloseHandle(p);
return;
}
}
As Hans Passant kindly reminds, the documentation already says that a completion routine must not be used if the directory is associated to a completion port. In this case I solved the problem by waiting on the completion port, i.e. ReadDirectoryChangesW(...,&overlapped,0);
Complete code is below.
while (true) {
OVERLAPPED overlapped;
memset(&overlapped,0,sizeof(overlapped));
BOOL success = ReadDirectoryChangesW(h,
&buffer[0],
4096*sizeof(DWORD),
FALSE, notifyFilter, 0, &overlapped,0);
if (!success) {
if (GetLastError()==ERROR_INVALID_HANDLE) {
//asynchronously closed by cancel
CloseHandle(p); //close completion port
return 0;
} else {
CloseHandle(h); //close directory handle
CloseHandle(p); //close completion port
return 1;
}
}
DWORD di;
LPOVERLAPPED lpOverlapped;
if (!GetQueuedCompletionStatus(p,&bytesReturned,&di,&lpOverlapped,1000)) {
int ret;
if (GetLastError()==WAIT_TIMEOUT) {
if (GetFileAttributes(directory)!=INVALID_FILE_ATTRIBUTES) {
continue; //timeout
} else {
//directory has been deleted or renamed
ret=0;
}
} else {
//other failure
ret=1;
}
CloseHandle(h); //close directory handle
CloseHandle(p); //close completion port
return ret;
}
char* ptr = (char*)&buffer[0];
char* end = ptr+bytesReturned;
while (ptr<end) {
FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION*) ptr;
//process FILE_NOTIFY_INFORMATION
ptr+=info->NextEntryOffset;
if (!info->NextEntryOffset) break;
}
}
Here is a concrete example:
I create a IWeBrowser2 interface by calling wb.CoCreateInstance(CLSID_InternetExplorer, 0, CLSCTX_SERVER);. This gives me a marshaled interface from my process into whichever of the running iexplore.exe processes happens to contain this browser tab in my thread A.
Now I use the IGlobalInterfaceTable to get a cookie for this interface, pass it to my thread B and request the marshaled interface from there.
Question: Do I get a proxy to the proxy in my thread A or directly to the instance in the IE process?
It seems sensible to me that I will get a direct proxy to the instance with its own reference to it, however:
If I end my thread A, the cookie I created there becomes invalid and I can't retrieve (and close) the interface pointers to the web browsers I created any more. This does not make sense unless there is a thunk in that thread that is destroyed when the thread quits.
Edit: Oh, both threads are STA.
I finally had some time to figure out what is happening, so I wrote a short test to see what is going on.
// MarshalTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
enum { WM_THEREYOUGO = WM_USER+1, WM_THANKYOU, WM_YOURWELCOME };
DWORD WINAPI TheOtherThread(DWORD * main_thread_id)
{
MSG msg = { 0 };
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
assert(SUCCEEDED(hr));
{
// create web browser
CComPtr<IWebBrowser2> wb;
hr = wb.CoCreateInstance(CLSID_InternetExplorer, 0, CLSCTX_SERVER);
assert(SUCCEEDED(hr) && wb);
// navigate
hr = wb->Navigate2(&CComVariant(_T("stackoverflow.com")), &CComVariant(0), &CComVariant(_T("")), &CComVariant(), &CComVariant());
assert(SUCCEEDED(hr));
hr = wb->put_Visible(VARIANT_TRUE);
assert(SUCCEEDED(hr));
// Marshal
DWORD the_cookie = 0;
{
CComPtr<IGlobalInterfaceTable> com_broker;
hr = com_broker.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
assert(SUCCEEDED(hr));
hr = com_broker->RegisterInterfaceInGlobal(wb, __uuidof(IWebBrowser2), &the_cookie);
}
// notify main thread
PostThreadMessage(*main_thread_id, WM_THEREYOUGO, the_cookie, NULL);
// message loop
while(GetMessage(&msg, 0, 0, 0)) {
if(msg.hwnd == NULL) {
// thread message
switch(msg.message) {
case WM_THANKYOU:
PostQuitMessage(0);
break;
}
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
CoUninitialize();
PostThreadMessage(*main_thread_id, WM_YOURWELCOME, 0, NULL);
return msg.wParam;
}
int _tmain(int argc, _TCHAR* argv[])
{
MSG msg = {0};
DWORD main_thread_id = GetCurrentThreadId();
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
assert(SUCCEEDED(hr));
{
DWORD ThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)TheOtherThread, &main_thread_id, 0, &ThreadId);
DWORD the_cookie = 0;
CComPtr<IWebBrowser2> wb, wb2;
while(GetMessage(&msg, 0, 0, 0)) {
if(msg.hwnd == NULL) {
// thread message
switch(msg.message) {
case WM_THEREYOUGO:
// we got the cookie.
the_cookie = msg.wParam;
// get the browser. This should work.
{
CComPtr<IGlobalInterfaceTable> com_broker;
hr = com_broker.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
assert(SUCCEEDED(hr));
hr = com_broker->GetInterfaceFromGlobal(the_cookie, __uuidof(IWebBrowser2), (void**)&wb);
assert(SUCCEEDED(hr) && wb);
}
// do something with it.
hr = wb->put_FullScreen(VARIANT_TRUE);
assert(SUCCEEDED(hr));
// signal the other thread.
PostThreadMessage(ThreadId, WM_THANKYOU, 0, NULL);
break;
case WM_YOURWELCOME:
// the other thread has ended.
PostQuitMessage(0);
break;
}
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// the other thread has ended. Try getting the interface again.
{
CComPtr<IGlobalInterfaceTable> com_broker;
hr = com_broker.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
assert(SUCCEEDED(hr));
hr = com_broker->GetInterfaceFromGlobal(the_cookie, __uuidof(IWebBrowser2), (void**)&wb2);
//assert(SUCCEEDED(hr) && wb2); // this fails, hr == E_INVALIDARG.
// clean up, will not be executed.
if(SUCCEEDED(hr)) {
hr = com_broker->RevokeInterfaceFromGlobal(the_cookie);
}
}
// try using it
if(wb2) {
hr = wb2->put_FullScreen(VARIANT_FALSE);
assert(SUCCEEDED(hr));
} else if(wb) {
// this succeeds
hr = wb->put_FullScreen(VARIANT_FALSE);
assert(SUCCEEDED(hr));
}
CloseHandle(hThread);
}
CoUninitialize();
return msg.wParam;
}
The bottom line is this:
Ending the thread that registered the interface invalidates the cookie.
The already marshaled interface stays valid. (In this case, that is.)
This means that I get a proxy to the IE process instead of to the other thread's object.
You already got a proxy on thread A since you asked for an out-of-process server. What happens next depends on the kind of apartment that thread A lives in, the argument to CoInitializeEx(). If it is MTA you will definitely get the same proxy in thread B, assuming it is MTA as well. The added reference count should keep it alive if Thread A exits. If it is STA then I'm not 100% sure but think you ought to get a new one. Easy to test btw, just use the one from thread A and you'll get RPC_E_WRONGTHREAD if a new one would have to be created.
I don't have a great explanation for why the thread A exit kills the proxy for thread B. Unless you call IGlobalInterfaceTable::RevokeInterfaceFromGlobal(). Which you'd normally do.