From d0e70c37abe3b6d52d952f5f17adb3d6edb0455a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Carl=20=C3=85stholm?= <[EMAIL REDACTED]>
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 | 106 ++++++++++++++------------
1 file changed, 56 insertions(+), 50 deletions(-)
diff --git a/src/main/windows/SDL_sysmain_runapp.c b/src/main/windows/SDL_sysmain_runapp.c
index d20553979daeb..5290c0a7e644b 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 <shellapi.h> // 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 */
-
- LPWSTR *argvw;
- char **argv;
- int i, argc, result;
-
- (void)_argc; (void)_argv; (void)reserved;
-
- argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
- if (!argvw) {
- return OutOfMemory();
- }
-
- /* 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.
- */
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments - aborting", NULL);
+ return -1;
+}
- // 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;
+int SDL_RunApp(int caller_argc, char *caller_argv[], SDL_main_func mainFunction, void * reserved)
+{
+ int result;
+ (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;
+
+ const LPWSTR command_line = GetCommandLineW();
+
+ // 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;
}
- }
- argv[i] = NULL;
- LocalFree(argvw);
-
- SDL_SetMainReady();
+ 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;
- // Run the application main() code
- result = mainFunction(argc, argv);
+ result = mainFunction(argc, argv);
- // Free argv, to avoid memory leak
- for (i = 0; i < argc; ++i) {
- HeapFree(GetProcessHeap(), 0, argv[i]);
+ cleanup:
+ HeapFree(GetProcessHeap(), 0, argv);
+ LocalFree(argvw);
}
- HeapFree(GetProcessHeap(), 0, argv);
return result;
}