comparison 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
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #pragma once
2
3 #ifdef FOOBAR2000_DESKTOP_WINDOWS
4
5 #include <libPPUI/win32_op.h>
6
7 namespace ProcessUtils {
8 class PipeIO : public stream_reader, public stream_writer {
9 public:
10 PFC_DECLARE_EXCEPTION(timeout, exception_io, "Timeout");
11 PipeIO(HANDLE handle, HANDLE hEvent, bool processMessages, DWORD timeOut = INFINITE) : m_handle(handle), m_event(hEvent), m_processMessages(processMessages), m_timeOut(timeOut) {
12 }
13 ~PipeIO() {
14 }
15
16 void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
17 if (p_bytes == 0) return;
18 OVERLAPPED ol = {};
19 ol.hEvent = m_event;
20 ResetEvent(m_event);
21 DWORD bytesWritten;
22 SetLastError(NO_ERROR);
23 if (WriteFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesWritten, &ol)) {
24 // succeeded already?
25 if (bytesWritten != p_bytes) throw exception_io();
26 return;
27 }
28
29 {
30 const DWORD code = GetLastError();
31 if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
32 }
33 const HANDLE handles[] = {m_event, abort.get_abort_event()};
34 SetLastError(NO_ERROR);
35 DWORD state = myWait(_countof(handles), handles);
36 if (state == WAIT_OBJECT_0) {
37 try {
38 WIN32_IO_OP( GetOverlappedResult(m_handle,&ol,&bytesWritten,TRUE) );
39 } catch(...) {
40 _cancel(ol);
41 throw;
42 }
43 if (bytesWritten != p_bytes) throw exception_io();
44 return;
45 }
46 _cancel(ol);
47 abort.check();
48 throw timeout();
49 }
50 size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
51 uint8_t * ptr = (uint8_t*) p_buffer;
52 size_t done = 0;
53 while(done < p_bytes) {
54 abort.check();
55 size_t delta = readPass(ptr + done, p_bytes - done, abort);
56 if (delta == 0) break;
57 done += delta;
58 }
59 return done;
60 }
61 size_t readPass(void * p_buffer,size_t p_bytes, abort_callback & abort) {
62 if (p_bytes == 0) return 0;
63 OVERLAPPED ol = {};
64 ol.hEvent = m_event;
65 ResetEvent(m_event);
66 DWORD bytesDone;
67 SetLastError(NO_ERROR);
68 if (ReadFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesDone, &ol)) {
69 // succeeded already?
70 return bytesDone;
71 }
72
73 {
74 const DWORD code = GetLastError();
75 switch(code) {
76 case ERROR_HANDLE_EOF:
77 return 0;
78 case ERROR_IO_PENDING:
79 break; // continue
80 default:
81 exception_io_from_win32(code);
82 };
83 }
84
85 const HANDLE handles[] = {m_event, abort.get_abort_event()};
86 SetLastError(NO_ERROR);
87 DWORD state = myWait(_countof(handles), handles);
88 if (state == WAIT_OBJECT_0) {
89 SetLastError(NO_ERROR);
90 if (!GetOverlappedResult(m_handle,&ol,&bytesDone,TRUE)) {
91 const DWORD code = GetLastError();
92 if (code == ERROR_HANDLE_EOF) bytesDone = 0;
93 else {
94 _cancel(ol);
95 exception_io_from_win32(code);
96 }
97 }
98 return bytesDone;
99 }
100 _cancel(ol);
101 abort.check();
102 throw timeout();
103 }
104 private:
105 DWORD myWait(DWORD count, const HANDLE * handles) {
106 if (m_processMessages) {
107 for(;;) {
108 DWORD state = MsgWaitForMultipleObjects(count, handles, FALSE, m_timeOut, QS_ALLINPUT);
109 if (state == WAIT_OBJECT_0 + count) {
110 ProcessPendingMessages();
111 } else {
112 return state;
113 }
114 }
115 } else {
116 return WaitForMultipleObjects(count, handles, FALSE, m_timeOut);
117 }
118 }
119 void _cancel(OVERLAPPED & ol) {
120 #if _WIN32_WINNT >= 0x600
121 CancelIoEx(m_handle,&ol);
122 #else
123 CancelIo(m_handle);
124 #endif
125 }
126 static void ProcessPendingMessages() {
127 MSG msg = {};
128 while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
129 }
130
131 HANDLE m_handle;
132 HANDLE m_event;
133 const DWORD m_timeOut;
134 const bool m_processMessages;
135 };
136
137 class SubProcess : public stream_reader, public stream_writer {
138 public:
139 PFC_DECLARE_EXCEPTION(failure, std::exception, "Unexpected failure");
140
141 SubProcess(const char * exePath, DWORD timeOutMS = 60*1000) : ExePath(exePath), hStdIn(), hStdOut(), hProcess(), ProcessMessages(false), TimeOutMS(timeOutMS) {
142 HANDLE ev;
143 WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
144 hEventRead = ev;
145 WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
146 hEventWrite = ev;
147 Restart();
148 }
149 void Restart() {
150 CleanUp();
151 STARTUPINFO si = {};
152 try {
153 si.cb = sizeof(si);
154 si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK;
155 //si.wShowWindow = SW_HIDE;
156
157 myCreatePipeOut(si.hStdInput, hStdIn);
158 myCreatePipeIn(hStdOut, si.hStdOutput);
159 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
160
161 PROCESS_INFORMATION pi = {};
162 try {
163 WIN32_OP( CreateProcess(pfc::stringcvt::string_os_from_utf8(ExePath), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) );
164 } catch(std::exception const & e) {
165 throw failure(PFC_string_formatter() << "Could not start the worker process - " << e);
166 }
167 hProcess = pi.hProcess; _Close(pi.hThread);
168 } catch(...) {
169 _Close(si.hStdInput);
170 _Close(si.hStdOutput);
171 CleanUp(); throw;
172 }
173 _Close(si.hStdInput);
174 _Close(si.hStdOutput);
175 }
176 ~SubProcess() {
177 CleanUp();
178 CloseHandle(hEventRead);
179 CloseHandle(hEventWrite);
180 }
181
182 bool IsRunning() const {
183 return hProcess != NULL;
184 }
185 void Detach() {
186 CleanUp(true);
187 }
188 bool ProcessMessages;
189 DWORD TimeOutMS;
190
191
192 void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
193 PipeIO writer(hStdIn, hEventWrite, ProcessMessages, TimeOutMS);
194 writer.write(p_buffer, p_bytes, abort);
195 }
196 size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
197 PipeIO reader(hStdOut, hEventRead, ProcessMessages, TimeOutMS);
198 return reader.read(p_buffer, p_bytes, abort);
199 }
200 void SetPriority(DWORD val) {
201 SetPriorityClass(hProcess, val);
202 }
203 protected:
204 HANDLE hStdIn, hStdOut, hProcess, hEventRead, hEventWrite;
205 const pfc::string8 ExePath;
206
207 void CleanUp(bool bDetach = false) {
208 _Close(hStdIn); _Close(hStdOut);
209 if (hProcess != NULL) {
210 if (!bDetach) {
211 if (WaitForSingleObject(hProcess, TimeOutMS) != WAIT_OBJECT_0) {
212 //PFC_ASSERT( !"Should not get here - worker stuck" );
213 FB2K_console_formatter() << pfc::string_filename_ext(ExePath) << " unresponsive - terminating";
214 TerminateProcess(hProcess, -1);
215 }
216 }
217 _Close(hProcess);
218 }
219 }
220 private:
221 static void _Close(HANDLE & h) {
222 if (h != NULL) {CloseHandle(h); h = NULL;}
223 }
224
225 static void myCreatePipe(HANDLE & in, HANDLE & out) {
226 SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
227 WIN32_OP( CreatePipe( &in, &out, &Attributes, 0 ) );
228 }
229
230 static pfc::string_formatter makePipeName() {
231 GUID id;
232 CoCreateGuid (&id);
233 return pfc::format( "\\\\.\\pipe\\", pfc::print_guid(id));
234 }
235
236 static void myCreatePipeOut(HANDLE & in, HANDLE & out) {
237 SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
238 const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
239 SetLastError(NO_ERROR);
240 HANDLE pipe = CreateNamedPipe(
241 pipeName,
242 FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
243 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
244 1,
245 1024*64,
246 1024*64,
247 NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
248 if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
249
250 in = CreateFile(pipeName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
251 DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS );
252 CloseHandle(pipe);
253 }
254
255 static void myCreatePipeIn(HANDLE & in, HANDLE & out) {
256 SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
257 const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
258 SetLastError(NO_ERROR);
259 HANDLE pipe = CreateNamedPipe(
260 pipeName,
261 FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
262 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
263 1,
264 1024*64,
265 1024*64,
266 NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
267 if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
268
269 out = CreateFile(pipeName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
270 DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &in, 0, FALSE, DUPLICATE_SAME_ACCESS );
271 CloseHandle(pipe);
272 }
273
274 PFC_CLASS_NOT_COPYABLE_EX(SubProcess)
275 };
276 }
277
278 #endif // FOOBAR2000_DESKTOP_WINDOWS
279