diff foosdk/sdk/foobar2000/helpers/ProcessUtils.h @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/helpers/ProcessUtils.h	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,279 @@
+#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::format( "\\\\.\\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
+