Atlas - SDL_windowsprocess.c
Home / ext / SDL / src / process / windows Lines: 3 | Size: 21577 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_PROCESS_WINDOWS 24 25#include "../../core/windows/SDL_windows.h" 26#include "../SDL_sysprocess.h" 27#include "../../io/SDL_iostream_c.h" 28 29#define READ_END 0 30#define WRITE_END 1 31 32struct SDL_ProcessData { 33 PROCESS_INFORMATION process_information; 34}; 35 36static void CleanupStream(void *userdata, void *value) 37{ 38 SDL_Process *process = (SDL_Process *)value; 39 const char *property = (const char *)userdata; 40 41 SDL_ClearProperty(process->props, property); 42} 43 44static bool SetupStream(SDL_Process *process, HANDLE handle, const char *mode, const char *property) 45{ 46 SDL_IOStream *io = SDL_IOFromHandle(handle, mode, true); 47 if (!io) { 48 return false; 49 } 50 51 SDL_SetPointerPropertyWithCleanup(SDL_GetIOProperties(io), "SDL.internal.process", process, CleanupStream, (void *)property); 52 SDL_SetPointerProperty(process->props, property, io); 53 return true; 54} 55 56static bool SetupRedirect(SDL_PropertiesID props, const char *property, HANDLE *result) 57{ 58 SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(props, property, NULL); 59 if (!io) { 60 SDL_SetError("%s is not set", property); 61 return false; 62 } 63 64 HANDLE handle = (HANDLE)SDL_GetPointerProperty(SDL_GetIOProperties(io), SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, INVALID_HANDLE_VALUE); 65 if (handle == INVALID_HANDLE_VALUE) { 66 SDL_SetError("%s doesn't have SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER available", property); 67 return false; 68 } 69 70 if (!DuplicateHandle(GetCurrentProcess(), handle, 71 GetCurrentProcess(), result, 72 0, TRUE, DUPLICATE_SAME_ACCESS)) { 73 WIN_SetError("DuplicateHandle()"); 74 return false; 75 } 76 77 if (GetFileType(*result) == FILE_TYPE_PIPE) { 78 DWORD wait_mode = PIPE_WAIT; 79 if (!SetNamedPipeHandleState(*result, &wait_mode, NULL, NULL)) { 80 WIN_SetError("SetNamedPipeHandleState()"); 81 return false; 82 } 83 } 84 return true; 85} 86 87static bool is_batch_file_path(const char *path) { 88 size_t len_path = SDL_strlen(path); 89 if (len_path < 4) { 90 return false; 91 } 92 if (SDL_strcasecmp(path + len_path - 4, ".bat") == 0 || SDL_strcasecmp(path + len_path - 4, ".cmd") == 0) { 93 return true; 94 } 95 return false; 96} 97 98static bool join_arguments(const char * const *args, LPWSTR *args_out) 99{ 100 size_t len; 101 int i; 102 size_t i_out; 103 char *result; 104 bool batch_file = is_batch_file_path(args[0]); 105 106 len = 0; 107 for (i = 0; args[i]; i++) { 108 const char *a = args[i]; 109 bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; 110 111 if (quotes) { 112 /* surround the argument with double quote if it is empty or contains whitespaces */ 113 len += 2; 114 } 115 116 for (; *a; a++) { 117 switch (*a) { 118 case '"': 119 len += 2; 120 break; 121 case '\\': 122 /* only escape backslashes that precede a double quote (including the enclosing double quote) */ 123 len += (a[1] == '"' || (quotes && a[1] == '\0')) ? 2 : 1; 124 break; 125 case ' ': 126 case '^': 127 case '&': 128 case '|': 129 case '<': 130 case '>': 131 if (batch_file) { 132 len += 2; 133 } else { 134 len += 1; 135 } 136 break; 137 default: 138 len += 1; 139 break; 140 } 141 } 142 /* space separator or final '\0' */ 143 len += 1; 144 } 145 146 result = SDL_malloc(len); 147 if (!result) { 148 *args_out = NULL; 149 return false; 150 } 151 152 i_out = 0; 153 for (i = 0; args[i]; i++) { 154 const char *a = args[i]; 155 bool quotes = *a == '\0' || SDL_strpbrk(a, " \r\n\t\v") != NULL; 156 157 if (quotes) { 158 result[i_out++] = '"'; 159 } 160 for (; *a; a++) { 161 switch (*a) { 162 case '"': 163 if (batch_file) { 164 result[i_out++] = '"'; 165 } else { 166 result[i_out++] = '\\'; 167 } 168 result[i_out++] = *a; 169 break; 170 case '\\': 171 result[i_out++] = *a; 172 if (a[1] == '"' || (quotes && a[1] == '\0')) { 173 result[i_out++] = *a; 174 } 175 break; 176 case ' ': 177 if (batch_file) { 178 result[i_out++] = '^'; 179 } 180 result[i_out++] = *a; 181 break; 182 case '^': 183 case '&': 184 case '|': 185 case '<': 186 case '>': 187 if (batch_file) { 188 result[i_out++] = '^'; 189 } 190 result[i_out++] = *a; 191 break; 192 default: 193 result[i_out++] = *a; 194 break; 195 } 196 } 197 if (quotes) { 198 result[i_out++] = '"'; 199 } 200 result[i_out++] = ' '; 201 } 202 SDL_assert(i_out == len); 203 result[len - 1] = '\0'; 204 205 *args_out = (LPWSTR)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)result, len); 206 SDL_free(result); 207 if (!args_out) { 208 return false; 209 } 210 return true; 211} 212 213static bool join_env(char **env, LPWSTR *env_out) 214{ 215 size_t len; 216 char **var; 217 char *result; 218 219 len = 0; 220 for (var = env; *var; var++) { 221 len += SDL_strlen(*var) + 1; 222 } 223 result = SDL_malloc(len + 1); 224 if (!result) { 225 return false; 226 } 227 228 len = 0; 229 for (var = env; *var; var++) { 230 size_t l = SDL_strlen(*var); 231 SDL_memcpy(result + len, *var, l); 232 result[len + l] = '\0'; 233 len += l + 1; 234 } 235 result[len] = '\0'; 236 237 *env_out = (LPWSTR)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)result, len); 238 SDL_free(result); 239 if (!*env_out) { 240 return false; 241 } 242 return true; 243} 244 245bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) 246{ 247 const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); 248 const char *cmdline = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_CMDLINE_STRING, NULL); 249 SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); 250 char **envp = NULL; 251 const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); 252 SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); 253 SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); 254 SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); 255 bool redirect_stderr = SDL_GetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, false) && 256 !SDL_HasProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER); 257 LPWSTR createprocess_cmdline = NULL; 258 LPWSTR createprocess_env = NULL; 259 LPWSTR createprocess_cwd = NULL; 260 STARTUPINFOW startup_info; 261 DWORD creation_flags; 262 SECURITY_ATTRIBUTES security_attributes; 263 HANDLE stdin_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; 264 HANDLE stdout_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; 265 HANDLE stderr_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; 266 HANDLE handle; 267 DWORD pipe_mode = PIPE_NOWAIT; 268 bool result = false; 269 270 // Keep the malloc() before exec() so that an OOM won't run a process at all 271 envp = SDL_GetEnvironmentVariables(env); 272 if (!envp) { 273 return false; 274 } 275 276 SDL_ProcessData *data = SDL_calloc(1, sizeof(*data)); 277 if (!data) { 278 SDL_free(envp); 279 return false; 280 } 281 process->internal = data; 282 data->process_information.hProcess = INVALID_HANDLE_VALUE; 283 data->process_information.hThread = INVALID_HANDLE_VALUE; 284 285 creation_flags = CREATE_UNICODE_ENVIRONMENT; 286 287 SDL_zero(startup_info); 288 startup_info.cb = sizeof(startup_info); 289 startup_info.dwFlags |= STARTF_USESTDHANDLES; 290 startup_info.hStdInput = INVALID_HANDLE_VALUE; 291 startup_info.hStdOutput = INVALID_HANDLE_VALUE; 292 startup_info.hStdError = INVALID_HANDLE_VALUE; 293 294 SDL_zero(security_attributes); 295 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); 296 security_attributes.bInheritHandle = TRUE; 297 security_attributes.lpSecurityDescriptor = NULL; 298 299 if (cmdline) { 300 createprocess_cmdline = WIN_UTF8ToString(cmdline); 301 if (!createprocess_cmdline) { 302 goto done; 303 } 304 } else if (!join_arguments(args, &createprocess_cmdline)) { 305 goto done; 306 } 307 308 if (!join_env(envp, &createprocess_env)) { 309 goto done; 310 } 311 312 if (working_directory) { 313 createprocess_cwd = WIN_UTF8ToStringW(working_directory); 314 if (!createprocess_cwd) { 315 goto done; 316 } 317 } 318 319 // Background processes don't have access to the terminal 320 // This isn't necessary on Windows, but we keep the same behavior as the POSIX implementation. 321 if (process->background) { 322 if (stdin_option == SDL_PROCESS_STDIO_INHERITED) { 323 stdin_option = SDL_PROCESS_STDIO_NULL; 324 } 325 if (stdout_option == SDL_PROCESS_STDIO_INHERITED) { 326 stdout_option = SDL_PROCESS_STDIO_NULL; 327 } 328 if (stderr_option == SDL_PROCESS_STDIO_INHERITED) { 329 stderr_option = SDL_PROCESS_STDIO_NULL; 330 } 331 creation_flags |= CREATE_NO_WINDOW; 332 } 333 334 switch (stdin_option) { 335 case SDL_PROCESS_STDIO_REDIRECT: 336 if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, &startup_info.hStdInput)) { 337 goto done; 338 } 339 break; 340 case SDL_PROCESS_STDIO_APP: 341 if (!CreatePipe(&stdin_pipe[READ_END], &stdin_pipe[WRITE_END], &security_attributes, 0)) { 342 stdin_pipe[READ_END] = INVALID_HANDLE_VALUE; 343 stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 344 goto done; 345 } 346 if (!SetNamedPipeHandleState(stdin_pipe[WRITE_END], &pipe_mode, NULL, NULL)) { 347 WIN_SetError("SetNamedPipeHandleState()"); 348 goto done; 349 } 350 if (!SetHandleInformation(stdin_pipe[WRITE_END], HANDLE_FLAG_INHERIT, 0) ) { 351 WIN_SetError("SetHandleInformation()"); 352 goto done; 353 } 354 startup_info.hStdInput = stdin_pipe[READ_END]; 355 break; 356 case SDL_PROCESS_STDIO_NULL: 357 startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); 358 break; 359 case SDL_PROCESS_STDIO_INHERITED: 360 default: 361 handle = GetStdHandle(STD_INPUT_HANDLE); 362 if (!handle) { 363 startup_info.hStdInput = NULL; 364 } else if (!DuplicateHandle(GetCurrentProcess(), handle, 365 GetCurrentProcess(), &startup_info.hStdInput, 366 0, TRUE, DUPLICATE_SAME_ACCESS)) { 367 startup_info.hStdInput = INVALID_HANDLE_VALUE; 368 WIN_SetError("DuplicateHandle()"); 369 goto done; 370 } 371 break; 372 } 373 374 switch (stdout_option) { 375 case SDL_PROCESS_STDIO_REDIRECT: 376 if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDOUT_POINTER, &startup_info.hStdOutput)) { 377 goto done; 378 } 379 break; 380 case SDL_PROCESS_STDIO_APP: 381 if (!CreatePipe(&stdout_pipe[READ_END], &stdout_pipe[WRITE_END], &security_attributes, 0)) { 382 stdout_pipe[READ_END] = INVALID_HANDLE_VALUE; 383 stdout_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 384 goto done; 385 } 386 if (!SetNamedPipeHandleState(stdout_pipe[READ_END], &pipe_mode, NULL, NULL)) { 387 WIN_SetError("SetNamedPipeHandleState()"); 388 goto done; 389 } 390 if (!SetHandleInformation(stdout_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) { 391 WIN_SetError("SetHandleInformation()"); 392 goto done; 393 } 394 startup_info.hStdOutput = stdout_pipe[WRITE_END]; 395 break; 396 case SDL_PROCESS_STDIO_NULL: 397 startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); 398 break; 399 case SDL_PROCESS_STDIO_INHERITED: 400 default: 401 handle = GetStdHandle(STD_OUTPUT_HANDLE); 402 if (!handle) { 403 startup_info.hStdOutput = NULL; 404 } else if (!DuplicateHandle(GetCurrentProcess(), handle, 405 GetCurrentProcess(), &startup_info.hStdOutput, 406 0, TRUE, DUPLICATE_SAME_ACCESS)) { 407 startup_info.hStdOutput = INVALID_HANDLE_VALUE; 408 WIN_SetError("DuplicateHandle()"); 409 goto done; 410 } 411 break; 412 } 413 414 if (redirect_stderr) { 415 handle = startup_info.hStdOutput; 416 if (!handle) { 417 startup_info.hStdError = NULL; 418 } else if (!DuplicateHandle(GetCurrentProcess(), handle, 419 GetCurrentProcess(), &startup_info.hStdError, 420 0, TRUE, DUPLICATE_SAME_ACCESS)) { 421 startup_info.hStdError = INVALID_HANDLE_VALUE; 422 WIN_SetError("DuplicateHandle()"); 423 goto done; 424 } 425 } else { 426 switch (stderr_option) { 427 case SDL_PROCESS_STDIO_REDIRECT: 428 if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDERR_POINTER, &startup_info.hStdError)) { 429 goto done; 430 } 431 break; 432 case SDL_PROCESS_STDIO_APP: 433 if (!CreatePipe(&stderr_pipe[READ_END], &stderr_pipe[WRITE_END], &security_attributes, 0)) { 434 stderr_pipe[READ_END] = INVALID_HANDLE_VALUE; 435 stderr_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 436 goto done; 437 } 438 if (!SetNamedPipeHandleState(stderr_pipe[READ_END], &pipe_mode, NULL, NULL)) { 439 WIN_SetError("SetNamedPipeHandleState()"); 440 goto done; 441 } 442 if (!SetHandleInformation(stderr_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) { 443 WIN_SetError("SetHandleInformation()"); 444 goto done; 445 } 446 startup_info.hStdError = stderr_pipe[WRITE_END]; 447 break; 448 case SDL_PROCESS_STDIO_NULL: 449 startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); 450 break; 451 case SDL_PROCESS_STDIO_INHERITED: 452 default: 453 handle = GetStdHandle(STD_ERROR_HANDLE); 454 if (!handle) { 455 startup_info.hStdError = NULL; 456 } else if (!DuplicateHandle(GetCurrentProcess(), handle, 457 GetCurrentProcess(), &startup_info.hStdError, 458 0, TRUE, DUPLICATE_SAME_ACCESS)) { 459 startup_info.hStdError = INVALID_HANDLE_VALUE; 460 WIN_SetError("DuplicateHandle()"); 461 goto done; 462 } 463 break; 464 } 465 } 466 467 if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, createprocess_cwd, &startup_info, &data->process_information)) { 468 WIN_SetError("CreateProcess"); 469 goto done; 470 } 471 472 SDL_SetNumberProperty(process->props, SDL_PROP_PROCESS_PID_NUMBER, data->process_information.dwProcessId); 473 474 if (stdin_option == SDL_PROCESS_STDIO_APP) { 475 if (!SetupStream(process, stdin_pipe[WRITE_END], "wb", SDL_PROP_PROCESS_STDIN_POINTER)) { 476 CloseHandle(stdin_pipe[WRITE_END]); 477 stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 478 } 479 } 480 if (stdout_option == SDL_PROCESS_STDIO_APP) { 481 if (!SetupStream(process, stdout_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDOUT_POINTER)) { 482 CloseHandle(stdout_pipe[READ_END]); 483 stdout_pipe[READ_END] = INVALID_HANDLE_VALUE; 484 } 485 } 486 if (stderr_option == SDL_PROCESS_STDIO_APP) { 487 if (!SetupStream(process, stderr_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDERR_POINTER)) { 488 CloseHandle(stderr_pipe[READ_END]); 489 stderr_pipe[READ_END] = INVALID_HANDLE_VALUE; 490 } 491 } 492 493 result = true; 494 495done: 496 if (startup_info.hStdInput != INVALID_HANDLE_VALUE && 497 startup_info.hStdInput != stdin_pipe[READ_END]) { 498 CloseHandle(startup_info.hStdInput); 499 } 500 if (startup_info.hStdOutput != INVALID_HANDLE_VALUE && 501 startup_info.hStdOutput != stdout_pipe[WRITE_END]) { 502 CloseHandle(startup_info.hStdOutput); 503 } 504 if (startup_info.hStdError != INVALID_HANDLE_VALUE && 505 startup_info.hStdError != stderr_pipe[WRITE_END]) { 506 CloseHandle(startup_info.hStdError); 507 } 508 if (stdin_pipe[READ_END] != INVALID_HANDLE_VALUE) { 509 CloseHandle(stdin_pipe[READ_END]); 510 } 511 if (stdout_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { 512 CloseHandle(stdout_pipe[WRITE_END]); 513 } 514 if (stderr_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { 515 CloseHandle(stderr_pipe[WRITE_END]); 516 } 517 SDL_free(createprocess_cmdline); 518 SDL_free(createprocess_env); 519 SDL_free(createprocess_cwd); 520 SDL_free(envp); 521 522 if (!result) { 523 if (stdin_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { 524 CloseHandle(stdin_pipe[WRITE_END]); 525 } 526 if (stdout_pipe[READ_END] != INVALID_HANDLE_VALUE) { 527 CloseHandle(stdout_pipe[READ_END]); 528 } 529 if (stderr_pipe[READ_END] != INVALID_HANDLE_VALUE) { 530 CloseHandle(stderr_pipe[READ_END]); 531 } 532 } 533 return result; 534} 535 536static BOOL CALLBACK terminate_app(HWND hwnd, LPARAM lparam) 537{ 538 DWORD current_proc_id = 0, *term_info = (DWORD *) lparam; 539 GetWindowThreadProcessId(hwnd, ¤t_proc_id); 540 if (current_proc_id == term_info[0] && PostMessage(hwnd, WM_CLOSE, 0, 0)) { 541 term_info[1]++; 542 } 543 return TRUE; 544} 545 546bool SDL_SYS_KillProcess(SDL_Process *process, bool force) 547{ 548 if (!force) { 549 // term_info[0] is the process ID, term_info[1] is number of successful tries 550 DWORD term_info[2]; 551 term_info[0] = process->internal->process_information.dwProcessId; 552 term_info[1] = 0; 553 EnumWindows(terminate_app, (LPARAM) &term_info); 554 if (term_info[1] || PostThreadMessage(process->internal->process_information.dwThreadId, WM_CLOSE, 0, 0)) { 555 return true; 556 } 557 if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, term_info[0])) { 558 return true; 559 } 560 } 561 if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { 562 return WIN_SetError("TerminateProcess failed"); 563 } 564 return true; 565} 566 567bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode) 568{ 569 DWORD result; 570 571 result = WaitForSingleObject(process->internal->process_information.hProcess, block ? INFINITE : 0); 572 573 if (result == WAIT_OBJECT_0) { 574 DWORD rc; 575 if (!GetExitCodeProcess(process->internal->process_information.hProcess, &rc)) { 576 return WIN_SetError("GetExitCodeProcess"); 577 } 578 if (exitcode) { 579 *exitcode = (int)rc; 580 } 581 return true; 582 } else if (result == WAIT_FAILED) { 583 return WIN_SetError("WaitForSingleObject(hProcess) returned WAIT_FAILED"); 584 } else { 585 SDL_ClearError(); 586 return false; 587 } 588} 589 590void SDL_SYS_DestroyProcess(SDL_Process *process) 591{ 592 SDL_ProcessData *data = process->internal; 593 SDL_IOStream *io; 594 595 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); 596 if (io) { 597 SDL_CloseIO(io); 598 } 599 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDERR_POINTER, NULL); 600 if (io) { 601 SDL_CloseIO(io); 602 } 603 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); 604 if (io) { 605 SDL_CloseIO(io); 606 } 607 if (data) { 608 if (data->process_information.hThread != INVALID_HANDLE_VALUE) { 609 CloseHandle(data->process_information.hThread); 610 } 611 if (data->process_information.hProcess != INVALID_HANDLE_VALUE) { 612 CloseHandle(data->process_information.hProcess); 613 } 614 } 615 SDL_free(data); 616} 617 618#endif // SDL_PROCESS_WINDOWS 619[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.