Skip to content

Commit dd53dbd

Browse files
committed
main: Rewrite the Windows implementation of SDL_RunApp()
This new implementation now only parses the command line and overrides the provided argv if the provided one is NULL. This enables programs that don't want to or can't use `SDL_main.h` to perform their own custom argument processing before explicitly calling `SDL_RunApp()`. If the program 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.
1 parent aa81cba commit dd53dbd

File tree

1 file changed

+56
-47
lines changed

1 file changed

+56
-47
lines changed

src/main/windows/SDL_sysmain_runapp.c

+56-47
Original file line numberDiff line numberDiff line change
@@ -25,74 +25,83 @@
2525
#include "../SDL_runapp.h"
2626
#include "../../core/windows/SDL_windows.h"
2727

28-
/* Win32-specific SDL_RunApp(), which does most of the SDL_main work,
29-
based on SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 */
30-
3128
#include <shellapi.h> // CommandLineToArgvW()
3229

33-
// Pop up an out of memory message, returns to Windows
3430
static int OutOfMemory(void)
3531
{
3632
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL);
3733
return -1;
3834
}
3935

40-
int MINGW32_FORCEALIGN SDL_RunApp(int _argc, char* _argv[], SDL_main_func mainFunction, void * reserved)
36+
static int ErrorProcessingCommandLine(void)
4137
{
42-
/* Gets the arguments with GetCommandLine, converts them to argc and argv
43-
and calls SDL_main */
44-
45-
LPWSTR *argvw;
46-
char **argv;
47-
int i, argc, result;
48-
49-
(void)_argc; (void)_argv; (void)reserved;
50-
51-
argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
52-
if (!argvw) {
53-
return OutOfMemory();
54-
}
55-
56-
/* Note that we need to be careful about how we allocate/free memory here.
57-
* If the application calls SDL_SetMemoryFunctions(), we can't rely on
58-
* SDL_free() to use the same allocator after SDL_main() returns.
59-
*/
38+
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL);
39+
return -1;
40+
}
6041

61-
// Parse it into argv and argc
62-
argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv));
63-
if (!argv) {
64-
return OutOfMemory();
65-
}
66-
for (i = 0; i < argc; ++i) {
67-
const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL);
68-
if (!utf8size) { // uhoh?
69-
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL);
70-
return -1;
42+
int MINGW32_FORCEALIGN SDL_RunApp(int caller_argc, char* caller_argv[], SDL_main_func mainFunction, void * reserved)
43+
{
44+
int result, argc;
45+
LPWSTR *argvw = NULL;
46+
char **argv = NULL;
47+
(void)reserved;
48+
49+
// Note that we need to be careful about how we allocate/free memory in this function. If the application calls
50+
// SDL_SetMemoryFunctions(), we can't rely on SDL_free() to use the same allocator after SDL_main() returns.
51+
52+
if (!caller_argv || caller_argc < 0) {
53+
// If the passed argv is NULL or argc is negative, the user expects SDL to get the command line arguments
54+
// using GetCommandLineW() and convert them to argc and argv before calling mainFunction().
55+
56+
// Because of how the Windows command line works, we know for sure that the buffer size required to store all
57+
// argument strings converted to UTF-8 (with null terminators) is guaranteed to be less than or equal to the
58+
// size of the original command line string converted to UTF-8.
59+
const int argdata_size = WideCharToMultiByte(CP_UTF8, 0, GetCommandLineW(), -1, NULL, 0, NULL, NULL); // Includes the null terminator
60+
if (!argdata_size) {
61+
result = ErrorProcessingCommandLine();
62+
goto cleanup;
7163
}
7264

73-
argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character.
74-
if (!argv[i]) {
75-
return OutOfMemory();
65+
argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
66+
if (!argvw || argc < 0) {
67+
result = OutOfMemory();
68+
goto cleanup;
7669
}
7770

78-
if (WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argv[i], utf8size, NULL, NULL) == 0) { // failed? uhoh!
79-
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL);
80-
return -1;
71+
// Allocate argv followed by the argument string buffer as one contiguous allocation.
72+
argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv) + argdata_size);
73+
if (!argv) {
74+
result = OutOfMemory();
75+
goto cleanup;
76+
}
77+
char *argdata = ((char *)argv) + (argc + 1) * sizeof(*argv);
78+
int argdata_index = 0;
79+
80+
for (int i = 0; i < argc; ++i) {
81+
const int bytes_written = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, argdata + argdata_index, argdata_size - argdata_index, NULL, NULL);
82+
if (!bytes_written) {
83+
result = ErrorProcessingCommandLine();
84+
goto cleanup;
85+
}
86+
argv[i] = argdata + argdata_index;
87+
argdata_index += bytes_written;
8188
}
89+
argv[argc] = NULL;
90+
91+
argvw = NULL;
92+
93+
caller_argc = argc;
94+
caller_argv = argv;
8295
}
83-
argv[i] = NULL;
84-
LocalFree(argvw);
8596

8697
SDL_SetMainReady();
8798

88-
// Run the application main() code
89-
result = SDL_CallMain(argc, argv, mainFunction);
99+
result = mainFunction(caller_argc, caller_argv); // No need for SDL_CallMain(); we already know that we have a valid argv
100+
101+
cleanup:
90102

91-
// Free argv, to avoid memory leak
92-
for (i = 0; i < argc; ++i) {
93-
HeapFree(GetProcessHeap(), 0, argv[i]);
94-
}
95103
HeapFree(GetProcessHeap(), 0, argv);
104+
LocalFree(argvw);
96105

97106
return result;
98107
}

0 commit comments

Comments
 (0)