Skip to content

Commit d0e70c3

Browse files
castholmicculus
authored andcommitted
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.
1 parent f0d958d commit d0e70c3

File tree

1 file changed

+56
-50
lines changed

1 file changed

+56
-50
lines changed

src/main/windows/SDL_sysmain_runapp.c

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,74 +24,80 @@
2424

2525
#include "../../core/windows/SDL_windows.h"
2626

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

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

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

60-
// Parse it into argv and argc
61-
argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv));
62-
if (!argv) {
63-
return OutOfMemory();
64-
}
65-
for (i = 0; i < argc; ++i) {
66-
const int utf8size = WideCharToMultiByte(CP_UTF8, 0, argvw[i], -1, NULL, 0, NULL, NULL);
67-
if (!utf8size) { // uhoh?
68-
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Error processing command line arguments", NULL);
69-
return -1;
41+
int SDL_RunApp(int caller_argc, char *caller_argv[], SDL_main_func mainFunction, void * reserved)
42+
{
43+
int result;
44+
(void)reserved;
45+
46+
// If the provided argv is valid, we pass it to the main function as-is, since it's probably what the user wants.
47+
// Otherwise, we take a NULL argv as an instruction for SDL to parse the command line into an argv.
48+
// On Windows, when SDL provides the main entry point, argv is always NULL.
49+
if (caller_argv && caller_argc >= 0) {
50+
result = mainFunction(caller_argc, caller_argv);
51+
} else {
52+
// We need to be careful about how we allocate/free memory here. We can't use SDL_alloc()/SDL_free()
53+
// because the application might have used SDL_SetMemoryFunctions() to change the allocator.
54+
LPWSTR *argvw = NULL;
55+
char **argv = NULL;
56+
57+
const LPWSTR command_line = GetCommandLineW();
58+
59+
// Because of how the Windows command line is structured, we know for sure that the buffer size required to
60+
// store all argument strings converted to UTF-8 (with null terminators) is guaranteed to be less than or equal
61+
// to the size of the original command line string converted to UTF-8.
62+
const int argdata_size = WideCharToMultiByte(CP_UTF8, 0, command_line, -1, NULL, 0, NULL, NULL); // Includes the null terminator
63+
if (!argdata_size) {
64+
result = ErrorProcessingCommandLine();
65+
goto cleanup;
7066
}
7167

72-
argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, utf8size); // this size includes the null-terminator character.
73-
if (!argv[i]) {
74-
return OutOfMemory();
68+
int argc;
69+
argvw = CommandLineToArgvW(command_line, &argc);
70+
if (!argvw || argc < 0) {
71+
result = OutOfMemory();
72+
goto cleanup;
7573
}
7674

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

87-
// Run the application main() code
88-
result = mainFunction(argc, argv);
95+
result = mainFunction(argc, argv);
8996

90-
// Free argv, to avoid memory leak
91-
for (i = 0; i < argc; ++i) {
92-
HeapFree(GetProcessHeap(), 0, argv[i]);
97+
cleanup:
98+
HeapFree(GetProcessHeap(), 0, argv);
99+
LocalFree(argvw);
93100
}
94-
HeapFree(GetProcessHeap(), 0, argv);
95101

96102
return result;
97103
}

0 commit comments

Comments
 (0)