diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 1e745e80d9623..0789a0925051f 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -41,9 +41,12 @@ * This is also where an app can be configured to use the main callbacks, via * the SDL_MAIN_USE_CALLBACKS macro. * - * SDL_main.h is a "single-header library," which is to say that including + * `SDL_main.h` is a "single-header library," which is to say that including * this header inserts code into your program, and you should only include it - * once in most cases. SDL.h does not include this header automatically. + * once in most cases. `SDL.h` does not include this header automatically. + * + * If you want to include `SDL_main.h` but don't want SDL to redefine main(), + * you can define SDL_MAIN_HANDLED before including `SDL_main.h`. * * For more information, see: * @@ -64,9 +67,10 @@ * Inform SDL that the app is providing an entry point instead of SDL. * * SDL does not define this macro, but will check if it is defined when - * including `SDL_main.h`. If defined, SDL will expect the app to provide the - * proper entry point for the platform, and all the other magic details - * needed, like manually calling SDL_SetMainReady. + * including `SDL_main.h`. If defined, SDL expects the app to provide a proper + * entry point for the platform that either calls SDL_RunApp() to let SDL + * initialize all platform-specific details, or initializes everything itself + * outside of SDL before finally calling SDL_SetMainReady(). * * Please see [README-main-functions](README-main-functions), (or * docs/README-main-functions.md in the source tree) for a more detailed @@ -115,7 +119,7 @@ * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MAIN_AVAILABLE +#define SDL_MAIN_AVAILABLE 1 /** * Defined if the target platform _requires_ a special mainline through SDL. @@ -134,7 +138,7 @@ * * \since This macro is available since SDL 3.2.0. */ -#define SDL_MAIN_NEEDED +#define SDL_MAIN_NEEDED 1 #endif @@ -250,12 +254,12 @@ * * \sa SDL_DECLSPEC */ -#define SDLMAIN_DECLSPEC +#define SDLMAIN_DECLSPEC SDL_DECLSPEC #elif defined(SDL_MAIN_EXPORTED) /* We need to export SDL_main so it can be launched from external code, like SDLActivity.java on Android */ -#define SDLMAIN_DECLSPEC SDL_DECLSPEC +#define SDLMAIN_DECLSPEC SDL_DECLSPEC #else /* usually this is empty */ #define SDLMAIN_DECLSPEC @@ -531,34 +535,46 @@ typedef int (SDLCALL *SDL_main_func)(int argc, char *argv[]); extern SDLMAIN_DECLSPEC int SDLCALL SDL_main(int argc, char *argv[]); /** - * Circumvent failure of SDL_Init() when not using SDL_main() as an entry - * point. + * Informs SDL that an app that provides its own entry point instead of using + * SDL_main() has initialized all platform-specific details outside of SDL. + * + * Apps that don't use SDL_main() should call SDL_SetMainReady() before + * calling SDL_Init(), otherwise SDL_Init() may fail on some platforms. * - * This function is defined in SDL_main.h, along with the preprocessor rule to - * redefine main() as SDL_main(). Thus to ensure that your main() function - * will not be changed it is necessary to define SDL_MAIN_HANDLED before - * including SDL.h. + * If your app provides its own entry point, consider instead using + * SDL_RunApp(), which automatically initializes all platform-specific details + * for you and is generally more portable and reliable than initializing + * everything on your own. When using SDL_RunApp(), you do *not* need to call + * SDL_SetMainReady(). * * \since This function is available since SDL 3.2.0. * + * \sa SDL_RunApp * \sa SDL_Init */ extern SDL_DECLSPEC void SDLCALL SDL_SetMainReady(void); /** * Initializes and launches an SDL application, by doing platform-specific - * initialization before calling your mainFunction and cleanups after it + * initialization before calling your `mainFunction` and cleanups after it * returns, if that is needed for a specific platform, otherwise it just calls - * mainFunction. + * `mainFunction`. * * You can use this if you want to use your own main() implementation without - * using SDL_main (like when using SDL_MAIN_HANDLED). When using this, you do - * *not* need SDL_SetMainReady(). + * using SDL_main() (like when using SDL_MAIN_HANDLED). When using this, you + * do *not* need to call SDL_SetMainReady(). + * + * If `argv` is non-NULL, SDL will pass it forward to `mainFunction` as-is. + * Otherwise, SDL will override `argv` in a platform-specific way. On some + * platforms like Windows, SDL may try to get the command line string and + * parse it into an argv. On others, it might just fall back to a dummy argv. + * Either way, SDL ensures that the argv passed to `mainFunction` is not NULL. * * \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 - * NULL if the platform's main-equivalent has no argv. + * NULL if the platform's main-equivalent has no argv (in which + * case SDL will override it in a platform-specific way). * \param mainFunction your SDL app's C-style main(). NOT the function you're * calling this from! Its name doesn't matter; it doesn't * literally have to be `main`. @@ -572,6 +588,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_SetMainReady(void); * process's initial thread. * * \since This function is available since SDL 3.2.0. + * + * \sa SDL_SetMainReady */ extern SDL_DECLSPEC int SDLCALL SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved); diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 2297fd1228bd7..9636fbd3f059d 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -846,41 +846,48 @@ JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, char **argv; bool isstack; - // Prepare the arguments. + // Always use the name "app_process" for argv[0] so PHYSFS_platformCalcBaseDir() works. + // https://github.com/love2d/love-android/issues/24 + len = (*env)->GetArrayLength(env, array); - argv = SDL_small_alloc(char *, 1 + len + 1, &isstack); // !!! FIXME: check for NULL - argc = 0; - /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works. - https://github.com/love2d/love-android/issues/24 - */ - argv[argc++] = SDL_strdup("app_process"); - for (i = 0; i < len; ++i) { - char *arg = NULL; - jstring string = (*env)->GetObjectArrayElement(env, array, i); - if (string) { - const char *utf = (*env)->GetStringUTFChars(env, string, 0); - if (utf) { - arg = SDL_strdup(utf); - (*env)->ReleaseStringUTFChars(env, string, utf); + argv = SDL_small_alloc(char *, 1 + len + 1, &isstack); + if (!argv) { + // Failed to allocate the argv (out of memory). Use a dummy argv instead. + char fallbackargv0[] = { 'a', 'p', 'p', '_', 'p', 'r', 'o', 'c', 'e', 's', 's', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; + + // Run the application (without arguments). + status = SDL_main(1, fallbackargv); + } else { + argc = 0; + argv[argc++] = SDL_strdup("app_process"); + for (i = 0; i < len; ++i) { + char *arg = NULL; + jstring string = (*env)->GetObjectArrayElement(env, array, i); + if (string) { + const char *utf = (*env)->GetStringUTFChars(env, string, 0); + if (utf) { + arg = SDL_strdup(utf); + (*env)->ReleaseStringUTFChars(env, string, utf); + } + (*env)->DeleteLocalRef(env, string); } - (*env)->DeleteLocalRef(env, string); - } - if (arg == NULL) { - arg = SDL_strdup(""); + if (arg == NULL) { + arg = SDL_strdup(""); + } + argv[argc++] = arg; } - argv[argc++] = arg; - } - argv[argc] = NULL; + argv[argc] = NULL; - // Run the application. - status = SDL_main(argc, argv); + // Run the application. + status = SDL_main(argc, argv); - // Release the arguments. - for (i = 0; i < argc; ++i) { - SDL_free(argv[i]); + // Release the arguments. + for (i = 0; i < argc; ++i) { + SDL_free(argv[i]); + } + SDL_small_free(argv, isstack); } - SDL_small_free(argv, isstack); - } else { __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file); } diff --git a/src/main/SDL_runapp.c b/src/main/SDL_runapp.c index eccdd3fccdcc0..50fe1651c715a 100644 --- a/src/main/SDL_runapp.c +++ b/src/main/SDL_runapp.c @@ -26,17 +26,16 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { + char fallbackargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; (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 }; + if (!argv || argc < 0) { argc = 1; - argv = argvdummy; + argv = fallbackargv; } + SDL_SetMainReady(); return mainFunction(argc, argv); } diff --git a/src/main/emscripten/SDL_sysmain_runapp.c b/src/main/emscripten/SDL_sysmain_runapp.c index 3b9c0fda89759..e778d57780337 100644 --- a/src/main/emscripten/SDL_sysmain_runapp.c +++ b/src/main/emscripten/SDL_sysmain_runapp.c @@ -33,8 +33,15 @@ EMSCRIPTEN_KEEPALIVE void force_free(void *ptr) { free(ptr); } int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { + char fallbackargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; (void)reserved; + if (!argv || argc < 0) { + argc = 1; + argv = fallbackargv; + } + // 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 @@ -55,6 +62,7 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserv } }, SDL_setenv_unsafe); + SDL_SetMainReady(); return mainFunction(argc, argv); } diff --git a/src/main/gdk/SDL_sysmain_runapp.cpp b/src/main/gdk/SDL_sysmain_runapp.cpp index 5a202259f9d23..0e0ffda592a87 100644 --- a/src/main/gdk/SDL_sysmain_runapp.cpp +++ b/src/main/gdk/SDL_sysmain_runapp.cpp @@ -30,119 +30,160 @@ extern "C" { #include // CommandLineToArgvW() #include -// Pop up an out of memory message, returns to Windows -static BOOL OutOfMemory(void) +static int OutOfMemory(void) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL); - return FALSE; + return -1; } -/* 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) +static int ErrorProcessingCommandLine(void) { - LPWSTR *argvw; - char **argv; - int i, argc, result; - HRESULT hr; - XTaskQueueHandle taskQueue; - - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argvw == NULL) { - return OutOfMemory(); - } + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments - aborting", NULL); + return -1; +} - /* 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. - */ +static int CallMainFunction(int caller_argc, char *caller_argv[], SDL_main_func mainFunction) +{ + int result; - // 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; + // 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); - - hr = XGameRuntimeInitialize(); - - if (SUCCEEDED(hr) && SDL_GetGDKTaskQueue(&taskQueue)) { - Uint32 titleid = 0; - char scidBuffer[64]; - XblInitArgs xblArgs; - - XTaskQueueSetCurrentProcessTaskQueue(taskQueue); - - // Try to get the title ID and initialize Xbox Live - hr = XGameGetXboxTitleId(&titleid); - if (SUCCEEDED(hr)) { - SDL_zero(xblArgs); - xblArgs.queue = taskQueue; - SDL_snprintf(scidBuffer, 64, "00000000-0000-0000-0000-0000%08X", titleid); - xblArgs.scid = scidBuffer; - hr = XblInitialize(&xblArgs); - } else { - SDL_SetError("[GDK] Unable to get titleid. Will not call XblInitialize. Check MicrosoftGame.config!"); + 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; - SDL_SetMainReady(); - - if (!GDK_RegisterChangeNotifications()) { - return -1; - } - - // Run the application main() code result = mainFunction(argc, argv); - GDK_UnregisterChangeNotifications(); + cleanup: + HeapFree(GetProcessHeap(), 0, argv); + LocalFree(argvw); + } + + return result; +} - // !!! FIXME: This follows the docs exactly, but for some reason still leaks handles on exit? - // Terminate the task queue and dispatch any pending tasks - XTaskQueueTerminate(taskQueue, false, nullptr, nullptr); - while (XTaskQueueDispatch(taskQueue, XTaskQueuePort::Completion, 0)) - ; +/* Gets the arguments with GetCommandLine, converts them to argc and argv + and calls SDL_main */ +extern "C" +int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *) +{ + int result; + HRESULT hr; - XTaskQueueCloseHandle(taskQueue); + hr = XGameRuntimeInitialize(); + if (SUCCEEDED(hr)) { + XTaskQueueHandle taskQueue; + if (SDL_GetGDKTaskQueue(&taskQueue)) { + XTaskQueueSetCurrentProcessTaskQueue(taskQueue); + + // Try to get the TitleID and initialize Xbox Live + Uint32 titleId; + bool xblInitialized = false; + hr = XGameGetXboxTitleId(&titleId); + if (SUCCEEDED(hr)) { + XblInitArgs xblInitArgs; + char scidBuffer[64]; + SDL_zero(xblInitArgs); + xblInitArgs.queue = taskQueue; + SDL_snprintf(scidBuffer, 64, "00000000-0000-0000-0000-0000%08X", titleId); + xblInitArgs.scid = scidBuffer; + hr = XblInitialize(&xblInitArgs); + if (SUCCEEDED(hr)) { + xblInitialized = true; + } else { + SDL_SetError("[GDK] Unable to call XblInitialize"); + } + } else { + SDL_SetError("[GDK] Unable to get TitleID. Will not call XblInitialize. Check MicrosoftGame.config!"); + } + + if (GDK_RegisterChangeNotifications()) { + // We are now ready to call the main function. + SDL_SetMainReady(); + result = CallMainFunction(argc, argv, mainFunction); + + GDK_UnregisterChangeNotifications(); + } else { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Unable to register change notifications - aborting", NULL); + result = -1; + } + + // Clean up Xbox Live (synchronously) + if (xblInitialized) { + XAsyncBlock asyncBlock = { 0 }; + hr = XblCleanupAsync(&asyncBlock); + if (SUCCEEDED(hr)) { + hr = XAsyncGetStatus(&asyncBlock, true); + } + } + + // Terminate the task queue and dispatch any pending tasks. + // !!! FIXME: This follows the docs exactly, but for some reason still leaks handles on exit? + hr = XTaskQueueTerminate(taskQueue, false, nullptr, nullptr); + while (XTaskQueueDispatch(taskQueue, XTaskQueuePort::Completion, 0)) + ; + XTaskQueueCloseHandle(taskQueue); + } else { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Could not create global task queue - aborting", NULL); + result = -1; + } XGameRuntimeUninitialize(); } else { #ifdef SDL_PLATFORM_WINGDK if (hr == E_GAMERUNTIME_DLL_NOT_FOUND) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Gaming Runtime library not found (xgameruntime.dll)", NULL); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Gaming Runtime library not found (xgameruntime.dll) - aborting", NULL); } else { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Could not initialize - aborting", NULL); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Could not initialize Gaming Runtime - aborting", NULL); } #else - SDL_assert_always(0 && "[GDK] Could not initialize - aborting"); + SDL_assert_always(0 && "[GDK] Could not initialize Gaming Runtime - aborting"); #endif result = -1; } - // Free argv, to avoid memory leak - for (i = 0; i < argc; ++i) { - HeapFree(GetProcessHeap(), 0, argv[i]); - } - 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..f4b883e147b36 100644 --- a/src/main/n3ds/SDL_sysmain_runapp.c +++ b/src/main/n3ds/SDL_sysmain_runapp.c @@ -27,7 +27,16 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { + char fallbackargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; int result; + (void)reserved; + + if (!argv || argc < 0) { + argc = 1; + argv = fallbackargv; + } + // init osSetSpeedupEnable(true); romfsInit(); diff --git a/src/main/ps2/SDL_sysmain_runapp.c b/src/main/ps2/SDL_sysmain_runapp.c index 658bce304f12d..3d4761d182a67 100644 --- a/src/main/ps2/SDL_sysmain_runapp.c +++ b/src/main/ps2/SDL_sysmain_runapp.c @@ -66,19 +66,25 @@ static void deinit_drivers(void) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { - int res; + char fallbackargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; + int result; (void)reserved; + if (!argv || argc < 0) { + argc = 1; + argv = fallbackargv; + } + prepare_IOP(); init_drivers(); SDL_SetMainReady(); - - res = mainFunction(argc, argv); + result = mainFunction(argc, argv); deinit_drivers(); - return res; + return result; } #endif // SDL_PLATFORM_PS2 diff --git a/src/main/psp/SDL_sysmain_runapp.c b/src/main/psp/SDL_sysmain_runapp.c index 7f09914b9334e..7713e0998b29f 100644 --- a/src/main/psp/SDL_sysmain_runapp.c +++ b/src/main/psp/SDL_sysmain_runapp.c @@ -71,11 +71,18 @@ int sdl_psp_setup_callbacks(void) int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void * reserved) { + char fallbackargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; (void)reserved; + + if (!argv || argc < 0) { + argc = 1; + argv = fallbackargv; + } + sdl_psp_setup_callbacks(); SDL_SetMainReady(); - return mainFunction(argc, argv); } diff --git a/src/main/windows/SDL_sysmain_runapp.c b/src/main/windows/SDL_sysmain_runapp.c index d20553979daeb..bc37c990216df 100644 --- a/src/main/windows/SDL_sysmain_runapp.c +++ b/src/main/windows/SDL_sysmain_runapp.c @@ -24,74 +24,82 @@ #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 */ - - LPWSTR *argvw; - char **argv; - int i, argc, result; - - (void)_argc; (void)_argv; (void)reserved; + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments - aborting", NULL); + return -1; +} - argvw = CommandLineToArgvW(GetCommandLineW(), &argc); - if (!argvw) { - return OutOfMemory(); - } +int SDL_RunApp(int caller_argc, char *caller_argv[], SDL_main_func mainFunction, void * reserved) +{ + int result; + (void)reserved; - /* 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_SetMainReady(); - // 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; + // 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; } diff --git a/src/video/uikit/SDL_uikitappdelegate.m b/src/video/uikit/SDL_uikitappdelegate.m index 3c1bb37366f4b..7ebed8c33f0c8 100644 --- a/src/video/uikit/SDL_uikitappdelegate.m +++ b/src/video/uikit/SDL_uikitappdelegate.m @@ -49,24 +49,31 @@ int SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserve * SDL_free() to use the same allocator after SDL_main() returns. */ 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++) { - forward_argv[i] = malloc((strlen(argv[i]) + 1) * sizeof(char)); // This should NOT be SDL_malloc() - strcpy(forward_argv[i], argv[i]); + if (argv && argc >= 0) { + forward_argc = argc; + forward_argv = (char **)malloc((argc + 1) * sizeof(char *)); // This should NOT be SDL_malloc() + for (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; + } else { + forward_argc = 0; + forward_argv = NULL; } - forward_argv[i] = NULL; // Give over control to run loop, SDLUIKitDelegate will handle most things from here @autoreleasepool { UIApplicationMain(argc, argv, nil, [SDLUIKitDelegate getAppDelegateClassName]); } - // free the memory we used to hold copies of argc and argv - for (i = 0; i < forward_argc; i++) { - free(forward_argv[i]); // This should NOT be SDL_free() + if (forward_argv) { + // free the memory we used to hold copies of argc and argv + for (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() } - free(forward_argv); // This should NOT be SDL_free() return exit_status; } @@ -395,7 +402,13 @@ - (void)postFinishLaunch // run the user's application, passing argc and argv SDL_SetiOSEventPump(true); - exit_status = forward_main(forward_argc, forward_argv); + if (forward_argv && forward_argc >= 0) { + exit_status = forward_main(forward_argc, forward_argv); + } else { + char fallbackargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' }; + char *fallbackargv[2] = { fallbackargv0, NULL }; + exit_status = forward_main(1, fallbackargv); + } SDL_SetiOSEventPump(false); if (launchWindow) {