244 lines
6.8 KiB
C++
244 lines
6.8 KiB
C++
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h> // for perror()
|
|
#include <string.h> // for memset()
|
|
#include <process.h> // for _beginthreadex(), _endthreadex()
|
|
|
|
// This class creates a child thread to read STDIN into a buffer.
|
|
// An event is set that can be monitored by the parent to know
|
|
// when input is available. Mutexes are used to regulate the
|
|
// thread's reading (i.e., flow control).
|
|
class Win32InputHandler
|
|
{
|
|
public:
|
|
Win32InputHandler();
|
|
~Win32InputHandler();
|
|
|
|
bool Open(int buflen = 4096);
|
|
void Close();
|
|
|
|
// The event handle returned here can be used in
|
|
// a call like WaitForSignalObject() to get notified
|
|
// when there is data to be read.
|
|
HANDLE GetEventHandle() const
|
|
{return input_event;}
|
|
|
|
// This does not block and the input_event
|
|
// handle is a cue for when to call it
|
|
// Returns number of bytes copied into "buffer"
|
|
// (-1 is returned upon input error (input closed))
|
|
int ReadData(char* buffer, int numBytes);
|
|
|
|
private:
|
|
static unsigned int __stdcall DoThreadOpen(void* param);
|
|
unsigned int Run();
|
|
|
|
HANDLE input_handle;
|
|
HANDLE input_event;
|
|
HANDLE thread_handle;
|
|
bool input_ready;
|
|
char* input_buffer;
|
|
int input_buflen;
|
|
int input_index;
|
|
int input_outdex;
|
|
CONDITION_VARIABLE buffer_vacancy;
|
|
CRITICAL_SECTION buffer_lock;
|
|
bool is_running;
|
|
int input_length;
|
|
bool input_error;
|
|
|
|
}; // end class Win32InputHandler
|
|
|
|
Win32InputHandler::Win32InputHandler()
|
|
: input_handle(NULL), input_event(NULL), thread_handle(NULL),
|
|
input_ready(false), input_buffer(NULL), input_buflen(0),
|
|
input_index(0), input_outdex(0), is_running(false),
|
|
input_length(0), input_error(false)
|
|
{
|
|
|
|
}
|
|
|
|
Win32InputHandler::~Win32InputHandler()
|
|
{
|
|
if (is_running) Close();
|
|
}
|
|
|
|
bool Win32InputHandler::Open(int buflen)
|
|
{
|
|
if (is_running) Close();
|
|
if (NULL != input_buffer)
|
|
{
|
|
input_buffer = NULL;
|
|
delete[] input_buffer;
|
|
}
|
|
input_buflen = input_index = input_outdex = input_length = 0;
|
|
input_error = false;
|
|
if (NULL != input_event) CloseHandle(input_event);
|
|
|
|
// Get the STDIN handle whatever it may be
|
|
input_handle = GetStdHandle(STD_INPUT_HANDLE);
|
|
/*if (FILE_TYPE_CHAR == GetFileType(input_handle))
|
|
{
|
|
// The STDIN is a console
|
|
DWORD consoleMode;
|
|
bool result = GetConsoleMode(input_handle, &consoleMode);
|
|
consoleMode &= ~(ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
|
|
SetConsoleMode(input_handle, consoleMode);
|
|
}*/
|
|
|
|
// Create an event handle, non-signaled initially
|
|
input_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (NULL == input_event)
|
|
{
|
|
perror("Win32InputHandler::Open() CreateEvent() error");
|
|
return false;
|
|
}
|
|
if (NULL == (input_buffer = new char[buflen]))
|
|
{
|
|
perror("Win32InputHandler::Open() new input_buffer error");
|
|
CloseHandle(input_event);
|
|
input_event = NULL;
|
|
return false;
|
|
}
|
|
input_buflen = buflen;
|
|
InitializeConditionVariable(&buffer_vacancy);
|
|
InitializeCriticalSection(&buffer_lock);
|
|
is_running = true;
|
|
if (0 == (thread_handle = (HANDLE)_beginthreadex(NULL, 0, DoThreadOpen, this, 0, NULL)))
|
|
{
|
|
perror("Win32InputHandler::Open() _beginthreadex() error");
|
|
DeleteCriticalSection(&buffer_lock);
|
|
delete[] input_buffer;
|
|
input_buffer = NULL;
|
|
CloseHandle(input_event);
|
|
input_event = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
} // end Win32InputHandler::Open()
|
|
|
|
void Win32InputHandler::Close()
|
|
{
|
|
EnterCriticalSection(&buffer_lock);
|
|
is_running = false;
|
|
WakeConditionVariable(&buffer_vacancy); // in case it was full
|
|
LeaveCriticalSection(&buffer_lock);
|
|
WaitForSingleObject(thread_handle, INFINITE);
|
|
DeleteCriticalSection(&buffer_lock);
|
|
delete[] input_buffer;
|
|
input_buffer = NULL;
|
|
CloseHandle(input_event);
|
|
input_event = NULL;
|
|
} // end Win32InputHandler::Close()
|
|
|
|
unsigned int __stdcall Win32InputHandler::DoThreadOpen(void* param)
|
|
{
|
|
Win32InputHandler* inputHandler = reinterpret_cast<Win32InputHandler*>(param);
|
|
unsigned int exitStatus = inputHandler->Run();
|
|
_endthreadex(exitStatus);
|
|
return exitStatus;
|
|
} // end Win32InputHandler::DoThreadOpen()
|
|
|
|
unsigned int Win32InputHandler::Run()
|
|
{
|
|
// This loop is for the child thread to read input one byte at a time
|
|
// (Since consoles/anonymous pipes don't support async i/o)
|
|
// The input bytes are copied to a circularly-managed "input_buffer"
|
|
// that can be accessed via the "ReadData()" method by the parent
|
|
while (is_running)
|
|
{
|
|
// Read input one byte at a time and buffer
|
|
DWORD dwRead = 0;
|
|
BOOL result = ReadFile(input_handle, input_buffer+input_index, 1, &dwRead, NULL);
|
|
EnterCriticalSection(&buffer_lock);
|
|
if (result)
|
|
{
|
|
if (0 != dwRead)
|
|
{
|
|
input_length++;
|
|
input_index++;
|
|
if (input_index >= input_buflen)
|
|
input_index = 0;
|
|
}
|
|
else
|
|
{
|
|
LeaveCriticalSection(&buffer_lock);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// What do we do on error?!
|
|
input_error = true;
|
|
is_running = false;
|
|
}
|
|
// Signals the parent of data ready (or error)
|
|
if (!input_ready)
|
|
{
|
|
SetEvent(input_event);
|
|
input_ready = true;
|
|
}
|
|
// If the buffer has been filled, this blocks this child
|
|
// loop until buffer space is available or it is stopped.
|
|
while ((input_index == input_outdex) && (0 != input_length) && is_running)
|
|
SleepConditionVariableCS(&buffer_vacancy, &buffer_lock, INFINITE);
|
|
LeaveCriticalSection(&buffer_lock);
|
|
} // end while(is_running)
|
|
return (input_error ? 1 : 0);
|
|
} // end Win32InputHandler::Run()
|
|
|
|
// Called by parent to gobble up data (non-blocking read)
|
|
// Returns number of bytes read or -1 on error (e.g., STDIN pipe closed)
|
|
int Win32InputHandler::ReadData(char* buffer, int numBytes)
|
|
{
|
|
EnterCriticalSection(&buffer_lock);
|
|
if ((0 != input_length) && (0 != numBytes))
|
|
{
|
|
if (numBytes > input_length)
|
|
numBytes = input_length;
|
|
input_length -= numBytes;
|
|
if (0 == input_length)
|
|
{
|
|
// On error condition, the event is left triggered
|
|
// after the input_buffer is emptied so parent gets
|
|
// all data buffered before the error (e.g., eof)
|
|
// occurred.
|
|
if (input_ready && !input_error)
|
|
{
|
|
input_ready = false;
|
|
ResetEvent(input_event);
|
|
}
|
|
}
|
|
// Copy "numBytes" of available data to caller's "buffer"
|
|
// (two-step process when input_buffer wraps)
|
|
unsigned int offset = input_outdex;
|
|
input_outdex += numBytes;
|
|
if (input_outdex >= input_buflen)
|
|
{
|
|
input_outdex -= input_buflen;
|
|
unsigned int count = numBytes - input_outdex;
|
|
memcpy(buffer, input_buffer+offset, count);
|
|
if (0 != input_outdex)
|
|
memcpy(buffer+count, input_buffer, input_outdex);
|
|
}
|
|
else
|
|
{
|
|
memcpy(buffer, input_buffer + offset, numBytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Doing the error check
|
|
if (input_error && (0 != numBytes))
|
|
numBytes = -1; // to indicate an error has occurred and child is dead
|
|
else
|
|
numBytes = 0;
|
|
}
|
|
// This wakes up the child if it is sleeping because the buffer was full
|
|
if (numBytes > 0)
|
|
WakeConditionVariable(&buffer_vacancy);
|
|
LeaveCriticalSection(&buffer_lock);
|
|
return numBytes;
|
|
} // end Win32InputHandler::ReadData()
|