From d0e70c37abe3b6d52d952f5f17adb3d6edb0455a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20=C3=85stholm?= Date: Sun, 26 Oct 2025 15:12:46 +0100 Subject: [PATCH] main: Rewrite the Windows implementation of `SDL_RunApp()` This new implementation only parses the command line into an argv when the provided argv is NULL. This lets programs that don't want to/can't include `SDL_main.h` to do their own custom argument processing before explicitly calling `SDL_RunApp()` without having SDL clobber the argv. If the user includes `SDL_main.h` as normal, the behavior remains the same as before (because `SDL_main_impl.h` passes a NULL argv). In addition, this new implementation performs fewer allocations and no longer leaks on failure. --- src/main/windows/SDL_sysmain_runapp.c | 104 ++++++++++++++------------ 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/src/main/windows/SDL_sysmain_runapp.c b/src/main/windows/SDL_sysmain_runapp.c index d20553979d..5290c0a7e6 100644 --- a/src/main/windows/SDL_sysmain_runapp.c +++ b/src/main/windows/SDL_sysmain_runapp.c @@ -24,74 +24,80 @@ #include "../../core/windows/SDL_windows.h" -/* Win32-specific SDL_RunApp(), which does most of the SDL_main work, - based on SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 */ - #include // CommandLineToArgvW() -// Pop up an out of memory message, returns to Windows static int OutOfMemory(void) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL); return -1; } -int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char *_argv[], SDL_main_func mainFunction, void * reserved) +static int ErrorProcessingCommandLine(void) { - /* Gets the arguments with GetCommandLine, converts them to argc and argv - and calls SDL_main */ + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments - aborting", NULL); + return -1; +} - LPWSTR *argvw; - char **argv; - int i, argc, result; +int SDL_RunApp(int caller_argc, char *caller_argv[], SDL_main_func mainFunction, void * reserved) +{ + int result; + (void)reserved; - (void)_argc; (void)_argv; (void)reserved; + // If the provided argv is valid, we pass it to the main function as-is, since it's probably what the user wants. + // Otherwise, we take a NULL argv as an instruction for SDL to parse the command line into an argv. + // On Windows, when SDL provides the main entry point, argv is always NULL. + if (caller_argv && caller_argc >= 0) { + result = mainFunction(caller_argc, caller_argv); + } else { + // We need to be careful about how we allocate/free memory here. We can't use SDL_alloc()/SDL_free() + // because the application might have used SDL_SetMemoryFunctions() to change the allocator. + LPWSTR *argvw = NULL; + char **argv = NULL; - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (!argvw) { - return OutOfMemory(); - } + const LPWSTR command_line = GetCommandLineW(); - /* Note that we need to be careful about how we allocate/free memory here. - * If the application calls SDL_SetMemoryFunctions(), we can't rely on - * SDL_free() to use the same allocator after SDL_main() returns. - */ - - // Parse it into argv and argc - argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (!argv) { - return OutOfMemory(); - } - for (i = 0; i < argc; ++i) { - const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL); - if (!utf8size) { // uhoh? - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; + // Because of how the Windows command line is structured, we know for sure that the buffer size required to + // store all argument strings converted to UTF-8 (with null terminators) is guaranteed to be less than or equal + // to the size of the original command line string converted to UTF-8. + const int argdata_size = WideCharToMultiByte(CP_UTF8, 0, command_line, -1, NULL, 0, NULL, NULL); // Includes the null terminator + if (!argdata_size) { + result = ErrorProcessingCommandLine(); + goto cleanup; } - argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. - if (!argv[i]) { - return OutOfMemory(); + int argc; + argvw = CommandLineToArgvW(command_line, &argc); + if (!argvw || argc < 0) { + result = OutOfMemory(); + goto cleanup; } - if (WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argv[i], utf8size, NULL, NULL) == 0) { // failed? uhoh! - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL); - return -1; + // Allocate argv followed by the argument string buffer as one contiguous allocation. + argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv) + argdata_size); + if (!argv) { + result = OutOfMemory(); + goto cleanup; } + char *argdata = ((char *)argv) + (argc + 1) * sizeof(*argv); + int argdata_index = 0; + + for (int i = 0; i < argc; ++i) { + const int bytes_written = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argdata + argdata_index, argdata_size - argdata_index, NULL, NULL); + if (!bytes_written) { + result = ErrorProcessingCommandLine(); + goto cleanup; + } + argv[i] = argdata + argdata_index; + argdata_index += bytes_written; + } + argv[argc] = NULL; + + result = mainFunction(argc, argv); + + cleanup: + HeapFree(GetProcessHeap(), 0, argv); + LocalFree(argvw); } - argv[i] = NULL; - LocalFree(argvw); - - SDL_SetMainReady(); - - // Run the application main() code - result = mainFunction(argc, argv); - - // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); - } - HeapFree(GetProcessHeap(), 0, argv); return result; }