From daa510b6df9246e6a60e56b89ad6a493e9d8d4c4 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Nov 2025 11:42:04 -0500 Subject: [PATCH] main: SDL_RunApp now explicitly handles NULL argv in all implementations. It'll usually replace it with `{ "SDL_app", NULL }`, but things like Win32 can query the OS for the original command line arguments. This allows apps/scripting languages that provide their own entry points to use SDL_RunApp and not have to worry about how to compose an argv array on things like Windows, when SDL was going to do it for them anyhow. Most things won't experience any change with this commit, including apps that that want extra control but originate in a standard main()-style entry point and can just pass the existing argc/argv through to SDL_RunApp. Windows isn't addressed here, since a previous commit already updated it. GDK has a different fix here, but we'll unify that in a later commit. Closes #12676. --- include/SDL3/SDL_main.h | 3 + src/main/SDL_main_callbacks.c | 10 +++ src/main/SDL_main_callbacks.h | 4 ++ src/main/SDL_runapp.c | 12 +--- src/main/emscripten/SDL_sysmain_runapp.c | 4 ++ src/main/gdk/SDL_sysmain_runapp.cpp | 84 ++++++++++++------------ src/main/n3ds/SDL_sysmain_runapp.c | 5 ++ src/main/ps2/SDL_sysmain_runapp.c | 4 ++ src/main/psp/SDL_sysmain_runapp.c | 4 ++ src/video/uikit/SDL_uikitappdelegate.m | 9 +-- 10 files changed, 83 insertions(+), 56 deletions(-) diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 1e745e80d9623..009cd4ef1a13e 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -555,6 +555,9 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetMainReady(void); * using SDL_main (like when using SDL_MAIN_HANDLED). When using this, you do * *not* need SDL_SetMainReady(). * + * If `argv` is NULL, SDL will provide command line arguments, either by + * querying the OS for them if possible, or supplying a filler array if not. + * * \param argc the argc parameter from the application's main() function, or 0 * if the platform's main-equivalent has no argc. * \param argv the argv parameter from the application's main() function, or diff --git a/src/main/SDL_main_callbacks.c b/src/main/SDL_main_callbacks.c index 913c9a71cb00c..6c4a67acd7b68 100644 --- a/src/main/SDL_main_callbacks.c +++ b/src/main/SDL_main_callbacks.c @@ -147,3 +147,13 @@ void SDL_QuitMainCallbacks(SDL_AppResult result) SDL_Quit(); } +void SDL_CheckDefaultArgcArgv(int *argc, char ***argv) +{ + if (!argv) + { + static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + static char *argvdummy[2] = { dummyargv0, NULL }; + *argc = 1; + *argv = argvdummy; + } +} diff --git a/src/main/SDL_main_callbacks.h b/src/main/SDL_main_callbacks.h index 1fd3725c259ee..2408414909a9a 100644 --- a/src/main/SDL_main_callbacks.h +++ b/src/main/SDL_main_callbacks.h @@ -27,6 +27,10 @@ SDL_AppResult SDL_InitMainCallbacks(int argc, char *argv[], SDL_AppInit_func app SDL_AppResult SDL_IterateMainCallbacks(bool pump_events); void SDL_QuitMainCallbacks(SDL_AppResult result); +// (not a callback thing, but convenient to stick this in here.) +// If *_argv is NULL, update *_argc and *_argv to point at a static array of { "SDL_app", NULL }. +void SDL_CheckDefaultArgcArgv(int *_argc, char ***_argv); + #endif // SDL_main_callbacks_h_ diff --git a/src/main/SDL_runapp.c b/src/main/SDL_runapp.c index eccdd3fccdcc0..83ac590cb5efc 100644 --- a/src/main/SDL_runapp.c +++ b/src/main/SDL_runapp.c @@ -19,6 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_internal.h" +#include "SDL_main_callbacks.h" /* Most platforms that use/need SDL_main have their own SDL_RunApp() implementation. * If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */ @@ -27,16 +28,7 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; - - if(!argv) - { - // make sure argv isn't NULL, in case some user code doesn't like that - static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; - static char *argvdummy[2] = { dummyargv0, NULL }; - argc = 1; - argv = argvdummy; - } - + SDL_CheckDefaultArgcArgv(&argc, &argv); return mainFunction(argc, argv); } diff --git a/src/main/emscripten/SDL_sysmain_runapp.c b/src/main/emscripten/SDL_sysmain_runapp.c index 3b9c0fda89759..92af40db8458f 100644 --- a/src/main/emscripten/SDL_sysmain_runapp.c +++ b/src/main/emscripten/SDL_sysmain_runapp.c @@ -22,6 +22,8 @@ #ifdef SDL_PLATFORM_EMSCRIPTEN +#include "../SDL_main_callbacks.h" + #include EM_JS_DEPS(sdlrunapp, "$dynCall,$stringToNewUTF8"); @@ -35,6 +37,8 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv { (void)reserved; + SDL_CheckDefaultArgcArgv(&argc, &argv); + // Move any URL params that start with "SDL_" over to environment // variables, so the hint system can pick them up, etc, much like a user // can set them from a shell prompt on a desktop machine. Ignore all diff --git a/src/main/gdk/SDL_sysmain_runapp.cpp b/src/main/gdk/SDL_sysmain_runapp.cpp index 5a202259f9d23..c4b4b905c1cf1 100644 --- a/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/src/main/gdk/SDL_sysmain_runapp.cpp @@ -37,53 +37,52 @@ static BOOL OutOfMemory(void) return FALSE; } -/* Gets the arguments with GetCommandLine, converts them to argc and argv - and calls SDL_main */ extern "C" -int SDL_RunApp(int, char **, SDL_main_func mainFunction, void *reserved) +int SDL_RunApp(int _argc, char **_argv, SDL_main_func mainFunction, void *reserved) { - LPWSTR *argvw; - char **argv; - int i, argc, result; - HRESULT hr; - XTaskQueueHandle taskQueue; - - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argvw == NULL) { - 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. - */ - - // Parse it into argv and argc - argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); - if (argv == NULL) { - 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; + char **allocated_argv = NULL; + char **argv = _argv; + int argc = _argc; + + if (!argv) { + // Get the arguments with GetCommandLine, convert them to argc and argv + LPWSTR *argvw = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argvw == NULL) { + return OutOfMemory(); } - argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. - if (!argv[i]) { + // 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. + + argv = allocated_argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv)); + if (argv == NULL) { return OutOfMemory(); } - - 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; + for (int 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; + } + + argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character. + if (!argv[i]) { + return OutOfMemory(); + } + + 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; + } } + argv[argc] = NULL; + LocalFree(argvw); } - argv[i] = NULL; - LocalFree(argvw); - hr = XGameRuntimeInitialize(); + int result = -1; + XTaskQueueHandle taskQueue; + HRESULT hr = XGameRuntimeInitialize(); if (SUCCEEDED(hr) && SDL_GetGDKTaskQueue(&taskQueue)) { Uint32 titleid = 0; @@ -134,14 +133,15 @@ int SDL_RunApp(int, char **, SDL_main_func mainFunction, void *reserved) #else SDL_assert_always(0 && "[GDK] Could not initialize - aborting"); #endif - result = -1; } // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); + if (allocated_argv) { + for (int i = 0; i < argc; ++i) { + HeapFree(GetProcessHeap(), 0, allocated_argv[i]); + } + HeapFree(GetProcessHeap(), 0, allocated_argv); } - HeapFree(GetProcessHeap(), 0, argv); return result; } diff --git a/src/main/n3ds/SDL_sysmain_runapp.c b/src/main/n3ds/SDL_sysmain_runapp.c index 06e4bf41e0891..4cb6adeb9560b 100644 --- a/src/main/n3ds/SDL_sysmain_runapp.c +++ b/src/main/n3ds/SDL_sysmain_runapp.c @@ -23,11 +23,16 @@ #ifdef SDL_PLATFORM_3DS +#include "../SDL_main_callbacks.h" + #include <3ds.h> int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { int result; + + SDL_CheckDefaultArgcArgv(&argc, &argv); + // init osSetSpeedupEnable(true); romfsInit(); diff --git a/src/main/ps2/SDL_sysmain_runapp.c b/src/main/ps2/SDL_sysmain_runapp.c index 658bce304f12d..e92bc702413ee 100644 --- a/src/main/ps2/SDL_sysmain_runapp.c +++ b/src/main/ps2/SDL_sysmain_runapp.c @@ -25,6 +25,8 @@ // SDL_RunApp() code for PS2 based on SDL_ps2_main.c, fjtrujy@gmail.com +#include "../SDL_main_callbacks.h" + #include #include #include @@ -69,6 +71,8 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv int res; (void)reserved; + SDL_CheckDefaultArgcArgv(&argc, &argv); + prepare_IOP(); init_drivers(); diff --git a/src/main/psp/SDL_sysmain_runapp.c b/src/main/psp/SDL_sysmain_runapp.c index 7f09914b9334e..3b4eb0f8b15d2 100644 --- a/src/main/psp/SDL_sysmain_runapp.c +++ b/src/main/psp/SDL_sysmain_runapp.c @@ -28,6 +28,7 @@ #include #include #include "../../events/SDL_events_c.h" +#include "../SDL_main_callbacks.h" /* If application's main() is redefined as SDL_main, and libSDL_main is linked, then this file will create the standard exit callback, @@ -72,6 +73,9 @@ int sdl_psp_setup_callbacks(void) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { (void)reserved; + + SDL_CheckDefaultArgcArgv(&argc, &argv); + sdl_psp_setup_callbacks(); SDL_SetMainReady(); diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index 2af7165890cef..e6223f1ada371 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -29,6 +29,7 @@ #import "SDL_uikitwindow.h" #include "../../events/SDL_events_c.h" +#include "../../main/SDL_main_callbacks.h" #ifdef main #undef main @@ -41,7 +42,7 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved) { - int i; + SDL_CheckDefaultArgcArgv(&argc, &argv); // store arguments /* Note that we need to be careful about how we allocate/free memory here. @@ -51,11 +52,11 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserve forward_main = mainFunction; forward_argc = argc; forward_argv = (char **)malloc((argc + 1) * sizeof(char *)); // This should NOT be SDL_malloc() - for (i = 0; i < argc; i++) { + for (int i = 0; i < argc; i++) { forward_argv[i] = malloc((strlen(argv[i]) + 1) * sizeof(char)); // This should NOT be SDL_malloc() strcpy(forward_argv[i], argv[i]); } - forward_argv[i] = NULL; + forward_argv[argc] = NULL; // Give over control to run loop, SDLUIKitDelegate will handle most things from here @autoreleasepool { @@ -71,7 +72,7 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserve } // free the memory we used to hold copies of argc and argv - for (i = 0; i < forward_argc; i++) { + for (int i = 0; i < forward_argc; i++) { free(forward_argv[i]); // This should NOT be SDL_free() } free(forward_argv); // This should NOT be SDL_free()