Browse Source

main: Unify command line handling between standard Windows and GDK targets.

Reference Issue #14510.
Ryan C. Gordon 3 weeks ago
parent
commit
17ab859907

+ 70 - 0
src/core/windows/SDL_windows.c

@@ -26,6 +26,8 @@
 
 
 #include "../../video/SDL_surface_c.h"
 #include "../../video/SDL_surface_c.h"
 
 
+#include <shellapi.h> // CommandLineToArgvW()
+
 #include <objbase.h> // for CoInitialize/CoUninitialize (Win32 only)
 #include <objbase.h> // for CoInitialize/CoUninitialize (Win32 only)
 #ifdef HAVE_ROAPI_H
 #ifdef HAVE_ROAPI_H
 #include <roapi.h> // For RoInitialize/RoUninitialize (Win32 only)
 #include <roapi.h> // For RoInitialize/RoUninitialize (Win32 only)
@@ -650,4 +652,72 @@ int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr,
     return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
     return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
 }
 }
 
 
+const char *WIN_CheckDefaultArgcArgv(int *pargc, char ***pargv, void **pallocated)
+{
+    // 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.
+
+    const char *out_of_mem_str = "Out of memory - aborting";
+    const char *proc_err_str = "Error processing command line arguments - aborting";
+
+    *pallocated = NULL;
+
+    if (*pargv) {
+        return NULL;  // just go with what was provided, no error message.
+    }
+
+    // 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) {
+        return proc_err_str;
+    }
+
+    int argc = -1;
+    argvw = CommandLineToArgvW(command_line, &argc);
+    if (!argvw || argc < 0) {
+        return out_of_mem_str;
+    }
+
+    // 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) {
+        LocalFree(argvw);
+        return out_of_mem_str;
+    }
+
+    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) {
+            HeapFree(GetProcessHeap(), 0, argv);
+            LocalFree(argvw);
+            return proc_err_str;
+        }
+        argv[i] = argdata + argdata_index;
+        argdata_index += bytes_written;
+    }
+
+    argv[argc] = NULL;
+
+    LocalFree(argvw);
+
+    *pargc = argc;
+    *pallocated = argv;
+    *pargv = argv;
+
+    return NULL;  // no error string.
+}
+
 #endif // defined(SDL_PLATFORM_WINDOWS)
 #endif // defined(SDL_PLATFORM_WINDOWS)

+ 3 - 0
src/core/windows/SDL_windows.h

@@ -209,6 +209,9 @@ extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);
 // WideCharToMultiByte, but with some WinXP management.
 // WideCharToMultiByte, but with some WinXP management.
 extern int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
 extern int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
 
 
+// parse out command lines from OS if argv is NULL, otherwise pass through unchanged. `*pallocated` must be HeapFree'd by caller if not NULL on successful return. Returns NULL on success, error string on problems.
+const char *WIN_CheckDefaultArgcArgv(int *pargc, char ***pargv, void **pallocated);
+
 // Ends C function definitions when using C++
 // Ends C function definitions when using C++
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 9 - 51
src/main/gdk/SDL_sysmain_runapp.cpp

@@ -30,54 +30,16 @@ extern "C" {
 #include <shellapi.h> // CommandLineToArgvW()
 #include <shellapi.h> // CommandLineToArgvW()
 #include <appnotify.h>
 #include <appnotify.h>
 
 
-// Pop up an out of memory message, returns to Windows
-static BOOL OutOfMemory(void)
-{
-    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL);
-    return FALSE;
-}
-
 extern "C"
 extern "C"
-int SDL_RunApp(int _argc, char **_argv, SDL_main_func mainFunction, void *reserved)
+int SDL_RunApp(int argc, char **argv, SDL_main_func mainFunction, void *reserved)
 {
 {
-    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();
-        }
+    (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.
-
-        argv = allocated_argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv));
-        if (argv == NULL) {
-            return OutOfMemory();
-        }
-        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);
+    void *heap_allocated = NULL;
+    const char *args_error = WIN_CheckDefaultArgcArgv(&argc, &argv, &heap_allocated);
+    if (args_error) {
+        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL);
+        return -1;
     }
     }
 
 
     int result = -1;
     int result = -1;
@@ -135,12 +97,8 @@ int SDL_RunApp(int _argc, char **_argv, SDL_main_func mainFunction, void *reserv
 #endif
 #endif
     }
     }
 
 
-    // Free argv, to avoid memory leak
-    if (allocated_argv) {
-        for (int i = 0; i < argc; ++i) {
-            HeapFree(GetProcessHeap(), 0, allocated_argv[i]);
-        }
-        HeapFree(GetProcessHeap(), 0, allocated_argv);
+    if (heap_allocated) {
+        HeapFree(GetProcessHeap(), 0, heap_allocated);
     }
     }
 
 
     return result;
     return result;

+ 9 - 69
src/main/windows/SDL_sysmain_runapp.c

@@ -27,82 +27,22 @@
 /* Win32-specific SDL_RunApp(), which does most of the SDL_main work,
 /* 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 */
   based on SDL_windows_main.c, placed in the public domain by Sam Lantinga  4/13/98 */
 
 
-#include <shellapi.h> // CommandLineToArgvW()
-
-static int OutOfMemory(void)
-{
-    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL);
-    return -1;
-}
-
-static int ErrorProcessingCommandLine(void)
-{
-    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments - aborting", NULL);
-    return -1;
-}
-
-int MINGW32_FORCEALIGN SDL_RunApp(int caller_argc, char *caller_argv[], SDL_main_func mainFunction, void * reserved)
+int MINGW32_FORCEALIGN SDL_RunApp(int argc, char *argv[], SDL_main_func mainFunction, void *reserved)
 {
 {
-    int result;
     (void)reserved;
     (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);
+    int result = -1;
+    void *heap_allocated = NULL;
+    const char *args_error = WIN_CheckDefaultArgcArgv(&argc, &argv, &heap_allocated);
+    if (args_error) {
+        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", args_error, NULL);
     } else {
     } 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;
-        }
-
-        int argc;
-        argvw = CommandLineToArgvW(command_line, &argc);
-        if (!argvw || argc < 0) {
-            result = OutOfMemory();
-            goto cleanup;
-        }
-
-        // 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;
-
         SDL_SetMainReady();
         SDL_SetMainReady();
         result = mainFunction(argc, argv);
         result = mainFunction(argc, argv);
-
-    cleanup:
-        HeapFree(GetProcessHeap(), 0, argv);
-        LocalFree(argvw);
+        if (heap_allocated) {
+            HeapFree(GetProcessHeap(), 0, heap_allocated);
+        }
     }
     }
-
     return result;
     return result;
 }
 }