Files
foobar2000-sdk/foobar2000/helpers/ProcessUtils.h
2021-12-14 00:28:25 -07:00

280 lines
8.2 KiB
C++

#pragma once
#ifdef FOOBAR2000_DESKTOP_WINDOWS
#include <libPPUI/win32_op.h>
namespace ProcessUtils {
class PipeIO : public stream_reader, public stream_writer {
public:
PFC_DECLARE_EXCEPTION(timeout, exception_io, "Timeout");
PipeIO(HANDLE handle, HANDLE hEvent, bool processMessages, DWORD timeOut = INFINITE) : m_handle(handle), m_event(hEvent), m_processMessages(processMessages), m_timeOut(timeOut) {
}
~PipeIO() {
}
void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
if (p_bytes == 0) return;
OVERLAPPED ol = {};
ol.hEvent = m_event;
ResetEvent(m_event);
DWORD bytesWritten;
SetLastError(NO_ERROR);
if (WriteFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesWritten, &ol)) {
// succeeded already?
if (bytesWritten != p_bytes) throw exception_io();
return;
}
{
const DWORD code = GetLastError();
if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
}
const HANDLE handles[] = {m_event, abort.get_abort_event()};
SetLastError(NO_ERROR);
DWORD state = myWait(_countof(handles), handles);
if (state == WAIT_OBJECT_0) {
try {
WIN32_IO_OP( GetOverlappedResult(m_handle,&ol,&bytesWritten,TRUE) );
} catch(...) {
_cancel(ol);
throw;
}
if (bytesWritten != p_bytes) throw exception_io();
return;
}
_cancel(ol);
abort.check();
throw timeout();
}
size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
uint8_t * ptr = (uint8_t*) p_buffer;
size_t done = 0;
while(done < p_bytes) {
abort.check();
size_t delta = readPass(ptr + done, p_bytes - done, abort);
if (delta == 0) break;
done += delta;
}
return done;
}
size_t readPass(void * p_buffer,size_t p_bytes, abort_callback & abort) {
if (p_bytes == 0) return 0;
OVERLAPPED ol = {};
ol.hEvent = m_event;
ResetEvent(m_event);
DWORD bytesDone;
SetLastError(NO_ERROR);
if (ReadFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesDone, &ol)) {
// succeeded already?
return bytesDone;
}
{
const DWORD code = GetLastError();
switch(code) {
case ERROR_HANDLE_EOF:
return 0;
case ERROR_IO_PENDING:
break; // continue
default:
exception_io_from_win32(code);
};
}
const HANDLE handles[] = {m_event, abort.get_abort_event()};
SetLastError(NO_ERROR);
DWORD state = myWait(_countof(handles), handles);
if (state == WAIT_OBJECT_0) {
SetLastError(NO_ERROR);
if (!GetOverlappedResult(m_handle,&ol,&bytesDone,TRUE)) {
const DWORD code = GetLastError();
if (code == ERROR_HANDLE_EOF) bytesDone = 0;
else {
_cancel(ol);
exception_io_from_win32(code);
}
}
return bytesDone;
}
_cancel(ol);
abort.check();
throw timeout();
}
private:
DWORD myWait(DWORD count, const HANDLE * handles) {
if (m_processMessages) {
for(;;) {
DWORD state = MsgWaitForMultipleObjects(count, handles, FALSE, m_timeOut, QS_ALLINPUT);
if (state == WAIT_OBJECT_0 + count) {
ProcessPendingMessages();
} else {
return state;
}
}
} else {
return WaitForMultipleObjects(count, handles, FALSE, m_timeOut);
}
}
void _cancel(OVERLAPPED & ol) {
#if _WIN32_WINNT >= 0x600
CancelIoEx(m_handle,&ol);
#else
CancelIo(m_handle);
#endif
}
static void ProcessPendingMessages() {
MSG msg = {};
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
}
HANDLE m_handle;
HANDLE m_event;
const DWORD m_timeOut;
const bool m_processMessages;
};
class SubProcess : public stream_reader, public stream_writer {
public:
PFC_DECLARE_EXCEPTION(failure, std::exception, "Unexpected failure");
SubProcess(const char * exePath, DWORD timeOutMS = 60*1000) : ExePath(exePath), hStdIn(), hStdOut(), hProcess(), ProcessMessages(false), TimeOutMS(timeOutMS) {
HANDLE ev;
WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
hEventRead = ev;
WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
hEventWrite = ev;
Restart();
}
void Restart() {
CleanUp();
STARTUPINFO si = {};
try {
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK;
//si.wShowWindow = SW_HIDE;
myCreatePipeOut(si.hStdInput, hStdIn);
myCreatePipeIn(hStdOut, si.hStdOutput);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
PROCESS_INFORMATION pi = {};
try {
WIN32_OP( CreateProcess(pfc::stringcvt::string_os_from_utf8(ExePath), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) );
} catch(std::exception const & e) {
throw failure(PFC_string_formatter() << "Could not start the worker process - " << e);
}
hProcess = pi.hProcess; _Close(pi.hThread);
} catch(...) {
_Close(si.hStdInput);
_Close(si.hStdOutput);
CleanUp(); throw;
}
_Close(si.hStdInput);
_Close(si.hStdOutput);
}
~SubProcess() {
CleanUp();
CloseHandle(hEventRead);
CloseHandle(hEventWrite);
}
bool IsRunning() const {
return hProcess != NULL;
}
void Detach() {
CleanUp(true);
}
bool ProcessMessages;
DWORD TimeOutMS;
void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
PipeIO writer(hStdIn, hEventWrite, ProcessMessages, TimeOutMS);
writer.write(p_buffer, p_bytes, abort);
}
size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
PipeIO reader(hStdOut, hEventRead, ProcessMessages, TimeOutMS);
return reader.read(p_buffer, p_bytes, abort);
}
void SetPriority(DWORD val) {
SetPriorityClass(hProcess, val);
}
protected:
HANDLE hStdIn, hStdOut, hProcess, hEventRead, hEventWrite;
const pfc::string8 ExePath;
void CleanUp(bool bDetach = false) {
_Close(hStdIn); _Close(hStdOut);
if (hProcess != NULL) {
if (!bDetach) {
if (WaitForSingleObject(hProcess, TimeOutMS) != WAIT_OBJECT_0) {
//PFC_ASSERT( !"Should not get here - worker stuck" );
FB2K_console_formatter() << pfc::string_filename_ext(ExePath) << " unresponsive - terminating";
TerminateProcess(hProcess, -1);
}
}
_Close(hProcess);
}
}
private:
static void _Close(HANDLE & h) {
if (h != NULL) {CloseHandle(h); h = NULL;}
}
static void myCreatePipe(HANDLE & in, HANDLE & out) {
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
WIN32_OP( CreatePipe( &in, &out, &Attributes, 0 ) );
}
static pfc::string_formatter makePipeName() {
GUID id;
CoCreateGuid (&id);
return PFC_string_formatter() << "\\\\.\\pipe\\" << pfc::print_guid(id);
}
static void myCreatePipeOut(HANDLE & in, HANDLE & out) {
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
SetLastError(NO_ERROR);
HANDLE pipe = CreateNamedPipe(
pipeName,
FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
1024*64,
1024*64,
NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
in = CreateFile(pipeName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS );
CloseHandle(pipe);
}
static void myCreatePipeIn(HANDLE & in, HANDLE & out) {
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
SetLastError(NO_ERROR);
HANDLE pipe = CreateNamedPipe(
pipeName,
FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
1024*64,
1024*64,
NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
out = CreateFile(pipeName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &in, 0, FALSE, DUPLICATE_SAME_ACCESS );
CloseHandle(pipe);
}
PFC_CLASS_NOT_COPYABLE_EX(SubProcess)
};
}
#endif // FOOBAR2000_DESKTOP_WINDOWS