Browse Source

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.
Carl Åstholm 1 month ago
parent
commit
d0e70c37ab
1 changed files with 56 additions and 50 deletions
  1. 56 50
      src/main/windows/SDL_sysmain_runapp.c

+ 56 - 50
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;
 }