Atlas - SDL_windowsprocess.c

Home / ext / SDL / src / process / windows Lines: 3 | Size: 21153 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 DWORD pipe_mode = PIPE_NOWAIT; 267 bool result = false; 268 269 // Keep the malloc() before exec() so that an OOM won't run a process at all 270 envp = SDL_GetEnvironmentVariables(env); 271 if (!envp) { 272 return false; 273 } 274 275 SDL_ProcessData *data = SDL_calloc(1, sizeof(*data)); 276 if (!data) { 277 SDL_free(envp); 278 return false; 279 } 280 process->internal = data; 281 data->process_information.hProcess = INVALID_HANDLE_VALUE; 282 data->process_information.hThread = INVALID_HANDLE_VALUE; 283 284 creation_flags = CREATE_UNICODE_ENVIRONMENT; 285 286 SDL_zero(startup_info); 287 startup_info.cb = sizeof(startup_info); 288 startup_info.dwFlags |= STARTF_USESTDHANDLES; 289 startup_info.hStdInput = INVALID_HANDLE_VALUE; 290 startup_info.hStdOutput = INVALID_HANDLE_VALUE; 291 startup_info.hStdError = INVALID_HANDLE_VALUE; 292 293 SDL_zero(security_attributes); 294 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); 295 security_attributes.bInheritHandle = TRUE; 296 security_attributes.lpSecurityDescriptor = NULL; 297 298 if (cmdline) { 299 createprocess_cmdline = WIN_UTF8ToString(cmdline); 300 if (!createprocess_cmdline) { 301 goto done; 302 } 303 } else if (!join_arguments(args, &createprocess_cmdline)) { 304 goto done; 305 } 306 307 if (!join_env(envp, &createprocess_env)) { 308 goto done; 309 } 310 311 if (working_directory) { 312 createprocess_cwd = WIN_UTF8ToStringW(working_directory); 313 if (!createprocess_cwd) { 314 goto done; 315 } 316 } 317 318 // Background processes don't have access to the terminal 319 // This isn't necessary on Windows, but we keep the same behavior as the POSIX implementation. 320 if (process->background) { 321 if (stdin_option == SDL_PROCESS_STDIO_INHERITED) { 322 stdin_option = SDL_PROCESS_STDIO_NULL; 323 } 324 if (stdout_option == SDL_PROCESS_STDIO_INHERITED) { 325 stdout_option = SDL_PROCESS_STDIO_NULL; 326 } 327 if (stderr_option == SDL_PROCESS_STDIO_INHERITED) { 328 stderr_option = SDL_PROCESS_STDIO_NULL; 329 } 330 creation_flags |= CREATE_NO_WINDOW; 331 } 332 333 switch (stdin_option) { 334 case SDL_PROCESS_STDIO_REDIRECT: 335 if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, &startup_info.hStdInput)) { 336 goto done; 337 } 338 break; 339 case SDL_PROCESS_STDIO_APP: 340 if (!CreatePipe(&stdin_pipe[READ_END], &stdin_pipe[WRITE_END], &security_attributes, 0)) { 341 stdin_pipe[READ_END] = INVALID_HANDLE_VALUE; 342 stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 343 goto done; 344 } 345 if (!SetNamedPipeHandleState(stdin_pipe[WRITE_END], &pipe_mode, NULL, NULL)) { 346 WIN_SetError("SetNamedPipeHandleState()"); 347 goto done; 348 } 349 if (!SetHandleInformation(stdin_pipe[WRITE_END], HANDLE_FLAG_INHERIT, 0) ) { 350 WIN_SetError("SetHandleInformation()"); 351 goto done; 352 } 353 startup_info.hStdInput = stdin_pipe[READ_END]; 354 break; 355 case SDL_PROCESS_STDIO_NULL: 356 startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); 357 break; 358 case SDL_PROCESS_STDIO_INHERITED: 359 default: 360 if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), 361 GetCurrentProcess(), &startup_info.hStdInput, 362 0, TRUE, DUPLICATE_SAME_ACCESS)) { 363 startup_info.hStdInput = INVALID_HANDLE_VALUE; 364 WIN_SetError("DuplicateHandle()"); 365 goto done; 366 } 367 break; 368 } 369 370 switch (stdout_option) { 371 case SDL_PROCESS_STDIO_REDIRECT: 372 if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDOUT_POINTER, &startup_info.hStdOutput)) { 373 goto done; 374 } 375 break; 376 case SDL_PROCESS_STDIO_APP: 377 if (!CreatePipe(&stdout_pipe[READ_END], &stdout_pipe[WRITE_END], &security_attributes, 0)) { 378 stdout_pipe[READ_END] = INVALID_HANDLE_VALUE; 379 stdout_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 380 goto done; 381 } 382 if (!SetNamedPipeHandleState(stdout_pipe[READ_END], &pipe_mode, NULL, NULL)) { 383 WIN_SetError("SetNamedPipeHandleState()"); 384 goto done; 385 } 386 if (!SetHandleInformation(stdout_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) { 387 WIN_SetError("SetHandleInformation()"); 388 goto done; 389 } 390 startup_info.hStdOutput = stdout_pipe[WRITE_END]; 391 break; 392 case SDL_PROCESS_STDIO_NULL: 393 startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); 394 break; 395 case SDL_PROCESS_STDIO_INHERITED: 396 default: 397 if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_OUTPUT_HANDLE), 398 GetCurrentProcess(), &startup_info.hStdOutput, 399 0, TRUE, DUPLICATE_SAME_ACCESS)) { 400 startup_info.hStdOutput = INVALID_HANDLE_VALUE; 401 WIN_SetError("DuplicateHandle()"); 402 goto done; 403 } 404 break; 405 } 406 407 if (redirect_stderr) { 408 if (!DuplicateHandle(GetCurrentProcess(), startup_info.hStdOutput, 409 GetCurrentProcess(), &startup_info.hStdError, 410 0, TRUE, DUPLICATE_SAME_ACCESS)) { 411 startup_info.hStdError = INVALID_HANDLE_VALUE; 412 WIN_SetError("DuplicateHandle()"); 413 goto done; 414 } 415 } else { 416 switch (stderr_option) { 417 case SDL_PROCESS_STDIO_REDIRECT: 418 if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDERR_POINTER, &startup_info.hStdError)) { 419 goto done; 420 } 421 break; 422 case SDL_PROCESS_STDIO_APP: 423 if (!CreatePipe(&stderr_pipe[READ_END], &stderr_pipe[WRITE_END], &security_attributes, 0)) { 424 stderr_pipe[READ_END] = INVALID_HANDLE_VALUE; 425 stderr_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 426 goto done; 427 } 428 if (!SetNamedPipeHandleState(stderr_pipe[READ_END], &pipe_mode, NULL, NULL)) { 429 WIN_SetError("SetNamedPipeHandleState()"); 430 goto done; 431 } 432 if (!SetHandleInformation(stderr_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) { 433 WIN_SetError("SetHandleInformation()"); 434 goto done; 435 } 436 startup_info.hStdError = stderr_pipe[WRITE_END]; 437 break; 438 case SDL_PROCESS_STDIO_NULL: 439 startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), (GENERIC_READ | GENERIC_WRITE), 0, &security_attributes, OPEN_EXISTING, 0, NULL); 440 break; 441 case SDL_PROCESS_STDIO_INHERITED: 442 default: 443 if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), 444 GetCurrentProcess(), &startup_info.hStdError, 445 0, TRUE, DUPLICATE_SAME_ACCESS)) { 446 startup_info.hStdError = INVALID_HANDLE_VALUE; 447 WIN_SetError("DuplicateHandle()"); 448 goto done; 449 } 450 break; 451 } 452 } 453 454 if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, createprocess_cwd, &startup_info, &data->process_information)) { 455 WIN_SetError("CreateProcess"); 456 goto done; 457 } 458 459 SDL_SetNumberProperty(process->props, SDL_PROP_PROCESS_PID_NUMBER, data->process_information.dwProcessId); 460 461 if (stdin_option == SDL_PROCESS_STDIO_APP) { 462 if (!SetupStream(process, stdin_pipe[WRITE_END], "wb", SDL_PROP_PROCESS_STDIN_POINTER)) { 463 CloseHandle(stdin_pipe[WRITE_END]); 464 stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE; 465 } 466 } 467 if (stdout_option == SDL_PROCESS_STDIO_APP) { 468 if (!SetupStream(process, stdout_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDOUT_POINTER)) { 469 CloseHandle(stdout_pipe[READ_END]); 470 stdout_pipe[READ_END] = INVALID_HANDLE_VALUE; 471 } 472 } 473 if (stderr_option == SDL_PROCESS_STDIO_APP) { 474 if (!SetupStream(process, stderr_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDERR_POINTER)) { 475 CloseHandle(stderr_pipe[READ_END]); 476 stderr_pipe[READ_END] = INVALID_HANDLE_VALUE; 477 } 478 } 479 480 result = true; 481 482done: 483 if (startup_info.hStdInput != INVALID_HANDLE_VALUE && 484 startup_info.hStdInput != stdin_pipe[READ_END]) { 485 CloseHandle(startup_info.hStdInput); 486 } 487 if (startup_info.hStdOutput != INVALID_HANDLE_VALUE && 488 startup_info.hStdOutput != stdout_pipe[WRITE_END]) { 489 CloseHandle(startup_info.hStdOutput); 490 } 491 if (startup_info.hStdError != INVALID_HANDLE_VALUE && 492 startup_info.hStdError != stderr_pipe[WRITE_END]) { 493 CloseHandle(startup_info.hStdError); 494 } 495 if (stdin_pipe[READ_END] != INVALID_HANDLE_VALUE) { 496 CloseHandle(stdin_pipe[READ_END]); 497 } 498 if (stdout_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { 499 CloseHandle(stdout_pipe[WRITE_END]); 500 } 501 if (stderr_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { 502 CloseHandle(stderr_pipe[WRITE_END]); 503 } 504 SDL_free(createprocess_cmdline); 505 SDL_free(createprocess_env); 506 SDL_free(createprocess_cwd); 507 SDL_free(envp); 508 509 if (!result) { 510 if (stdin_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { 511 CloseHandle(stdin_pipe[WRITE_END]); 512 } 513 if (stdout_pipe[READ_END] != INVALID_HANDLE_VALUE) { 514 CloseHandle(stdout_pipe[READ_END]); 515 } 516 if (stderr_pipe[READ_END] != INVALID_HANDLE_VALUE) { 517 CloseHandle(stderr_pipe[READ_END]); 518 } 519 } 520 return result; 521} 522 523static BOOL CALLBACK terminate_app(HWND hwnd, LPARAM lparam) 524{ 525 DWORD current_proc_id = 0, *term_info = (DWORD *) lparam; 526 GetWindowThreadProcessId(hwnd, &current_proc_id); 527 if (current_proc_id == term_info[0] && PostMessage(hwnd, WM_CLOSE, 0, 0)) { 528 term_info[1]++; 529 } 530 return TRUE; 531} 532 533bool SDL_SYS_KillProcess(SDL_Process *process, bool force) 534{ 535 if (!force) { 536 // term_info[0] is the process ID, term_info[1] is number of successful tries 537 DWORD term_info[2]; 538 term_info[0] = process->internal->process_information.dwProcessId; 539 term_info[1] = 0; 540 EnumWindows(terminate_app, (LPARAM) &term_info); 541 if (term_info[1] || PostThreadMessage(process->internal->process_information.dwThreadId, WM_CLOSE, 0, 0)) { 542 return true; 543 } 544 if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, term_info[0])) { 545 return true; 546 } 547 } 548 if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { 549 return WIN_SetError("TerminateProcess failed"); 550 } 551 return true; 552} 553 554bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode) 555{ 556 DWORD result; 557 558 result = WaitForSingleObject(process->internal->process_information.hProcess, block ? INFINITE : 0); 559 560 if (result == WAIT_OBJECT_0) { 561 DWORD rc; 562 if (!GetExitCodeProcess(process->internal->process_information.hProcess, &rc)) { 563 return WIN_SetError("GetExitCodeProcess"); 564 } 565 if (exitcode) { 566 *exitcode = (int)rc; 567 } 568 return true; 569 } else if (result == WAIT_FAILED) { 570 return WIN_SetError("WaitForSingleObject(hProcess) returned WAIT_FAILED"); 571 } else { 572 SDL_ClearError(); 573 return false; 574 } 575} 576 577void SDL_SYS_DestroyProcess(SDL_Process *process) 578{ 579 SDL_ProcessData *data = process->internal; 580 SDL_IOStream *io; 581 582 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); 583 if (io) { 584 SDL_CloseIO(io); 585 } 586 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDERR_POINTER, NULL); 587 if (io) { 588 SDL_CloseIO(io); 589 } 590 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); 591 if (io) { 592 SDL_CloseIO(io); 593 } 594 if (data) { 595 if (data->process_information.hThread != INVALID_HANDLE_VALUE) { 596 CloseHandle(data->process_information.hThread); 597 } 598 if (data->process_information.hProcess != INVALID_HANDLE_VALUE) { 599 CloseHandle(data->process_information.hProcess); 600 } 601 } 602 SDL_free(data); 603} 604 605#endif // SDL_PROCESS_WINDOWS 606
[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.