#pragma once #ifdef FOOBAR2000_DESKTOP_WINDOWS #include 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(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(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