SDL: Implement SDL_main as header-only lib for Win32

From ca2fe7be1a68d12a5ffb91f0db73019971bb9f96 Mon Sep 17 00:00:00 2001
From: Daniel Gibson <[EMAIL REDACTED]>
Date: Sun, 4 Dec 2022 03:29:22 +0100
Subject: [PATCH] Implement SDL_main as header-only lib for Win32

(remaining platforms will follow)

SDL_main.h is *not* included by SDL.h anymore, users are supposed to
include it directly now, usually only in the file they implement main() in.
If they need the header elsewhere or don't want SDL_main to implement
main() (but only call SDL_SetMainReady() or whatever), they
can #define SDL_MAIN_HANDLED first, same as before.
For SDL-internal usage, I added _SDL_MAIN_NOIMPL, which *also* skips the
implementation and `#define main SDL_main`, but still defines
SDL_MAIN_AVAILABLE and SDL_MAIN_NEEDED in SDL_main.h, as before.

To make the implementaion in the header shorter and avoid including windows.h,
I moved most of the Win32 SDL_main code into SDL3.dll via SDL_Win32RunApp(),
so the header-only part is just the different main functions calling
SDL_Win32RunApp(SDL_main, NULL)

Note that I changed changed the return value and type of OutOfMemory()
to return -1 instead of FALSE, so main() (or WinMain() or whatever)
returns -1 instead of 0 in case of an out-of-memory error

Compared to original Win32 SDL_main, I tweaked the part of the
implementation in SDL_main_impl.h a bit to avoid linker warnings
and conflicts with stuff from windows.h:

- replaced windows.h with own define of WINAPI
  and typedef-ing HINSTANCE and LPSTR.
  This prevents conflicts between all the generically-named #defines and
  types in windows.h and user code (like DrawState in some SDL tests)
- only using one of main() or wmain() gets rid of a MSVC linker error
  ("warning LNK4067: ambiguous entry point")
  If this still causes problems, we might try getting rid of wmain(),
  seemed to me like MSVC can use regular main() in UNICODE mode as well
- simplified the UNICODE logic for that - while this is not exactly
  equivalent to the old, it should make sense and Works For Me
---
 include/SDL3/SDL.h                  |   1 -
 include/SDL3/SDL_main.h             |  23 ++++++
 include/SDL3/SDL_main_impl.h        | 107 ++++++++++++++++++++++++++++
 src/SDL_internal.h                  |   2 +
 src/core/windows/SDL_windows.c      |  72 +++++++++++++++++++
 src/dynapi/SDL_dynapi.c             |   3 +
 src/dynapi/SDL_dynapi.sym           |   1 +
 src/dynapi/SDL_dynapi_overrides.h   |   1 +
 src/dynapi/SDL_dynapi_procs.h       |   3 +
 src/main/windows/SDL_windows_main.c | 106 +--------------------------
 10 files changed, 214 insertions(+), 105 deletions(-)
 create mode 100644 include/SDL3/SDL_main_impl.h

diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h
index e69242e45502..5f44e5e61719 100644
--- a/include/SDL3/SDL.h
+++ b/include/SDL3/SDL.h
@@ -29,7 +29,6 @@
 #ifndef SDL_h_
 #define SDL_h_
 
-#include <SDL3/SDL_main.h>
 #include <SDL3/SDL_stdinc.h>
 #include <SDL3/SDL_assert.h>
 #include <SDL3/SDL_atomic.h>
diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h
index 25818b82027d..d90c0ecb432b 100644
--- a/include/SDL3/SDL_main.h
+++ b/include/SDL3/SDL_main.h
@@ -207,6 +207,21 @@ extern DECLSPEC void SDLCALL SDL_UnregisterApp(void);
 
 #endif /* defined(__WIN32__) || defined(__GDK__) */
 
+#ifdef __WIN32__
+
+/**
+ * Initialize and launch an SDL/Win32 (classic WinAPI) application.
+ *
+ * \param mainFunction the SDL app's C-style main(), an SDL_main_func
+ * \param reserved reserved for future use; should be NULL
+ * \returns 0 on success or -1 on failure; call SDL_GetError() to retrieve
+ *          more information on the failure.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_Win32RunApp(SDL_main_func mainFunction, void * reserved);
+
+#endif /* __WIN32__ */
 
 #ifdef __WINRT__
 
@@ -266,8 +281,16 @@ extern DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
 #ifdef __cplusplus
 }
 #endif
+
 #include <SDL3/close_code.h>
 
+#if !defined(SDL_MAIN_HANDLED) && !defined(_SDL_MAIN_NOIMPL)
+/* include header-only SDL_main implementations */
+#if defined(__WIN32__) || defined(__GDK__) /* TODO: other platforms */
+#include <SDL3/SDL_main_impl.h>
+#endif
+#endif /* SDL_MAIN_HANDLED */
+
 #endif /* SDL_main_h_ */
 
 /* vi: set ts=4 sw=4 expandtab: */
diff --git a/include/SDL3/SDL_main_impl.h b/include/SDL3/SDL_main_impl.h
new file mode 100644
index 000000000000..de4e8d972238
--- /dev/null
+++ b/include/SDL3/SDL_main_impl.h
@@ -0,0 +1,107 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef SDL_main_windows_h_
+#define SDL_main_windows_h_
+
+#if !defined(SDL_main_h_)
+#error "This header should not be included directly, but only via SDL_main.h!"
+#endif
+
+/* if someone wants to include SDL_main.h but doesn't want the main handing magic,
+   (maybe to call SDL_RegisterApp()) they can #define SDL_MAIN_HANDLED first
+   _SDL_MAIN_NOIMPL is for SDL-internal usage (only affects implementation,
+   not definition of SDL_MAIN_AVAILABLE etc in SDL_main.h) */
+#if !defined(SDL_MAIN_HANDLED) && !defined(_SDL_MAIN_NOIMPL)
+
+#if defined(__WIN32__)
+
+/* these defines/typedefs are needed for the WinMain() definition */
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+
+typedef struct HINSTANCE__ * HINSTANCE;
+typedef char* LPSTR;
+
+#ifdef main
+#  undef main
+#endif /* main */
+
+#include <SDL3/begin_code.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#if defined(_MSC_VER)
+/* The VC++ compiler needs main/wmain defined */
+# define console_ansi_main main
+# if defined(UNICODE) && UNICODE
+#  define console_wmain wmain
+# endif
+#endif
+
+#if defined( UNICODE ) && UNICODE
+/* This is where execution begins [console apps, unicode] */
+int
+console_wmain(int argc, wchar_t *wargv[], wchar_t *wenvp)
+{
+    return SDL_Win32RunApp(SDL_main, NULL);
+}
+
+#else /* ANSI */
+
+/* This is where execution begins [console apps, ansi] */
+int
+console_ansi_main(int argc, char *argv[])
+{
+    return SDL_Win32RunApp(SDL_main, NULL);
+}
+#endif /* UNICODE/ANSI */
+
+/* This is where execution begins [windowed apps] */
+int WINAPI
+WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
+{
+    return SDL_Win32RunApp(SDL_main, NULL);
+}
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include <SDL3/close_code.h>
+
+/* rename users main() function to SDL_main() so it can be called from the wrapper above */
+#define main    SDL_main
+
+
+#elif 1 /* end of __WIN32__ impl - TODO: other platforms */
+
+#endif /* __WIN32__ etc */
+
+#endif /* SDL_MAIN_HANDLED */
+
+#endif /* SDL_main_windows_h_ */
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/SDL_internal.h b/src/SDL_internal.h
index 11470af8dbe4..855aa0c03af0 100644
--- a/src/SDL_internal.h
+++ b/src/SDL_internal.h
@@ -181,6 +181,8 @@
 #endif
 
 #include <SDL3/SDL.h>
+#define _SDL_MAIN_NOIMPL /* don't drag in header-only implementation of SDL_main */
+#include <SDL3/SDL_main.h>
 
 /* The internal implementations of these functions have up to nanosecond precision.
    We can expose these functions as part of the API if we want to later.
diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c
index 3dbe23cfc034..5d417774bd01 100644
--- a/src/core/windows/SDL_windows.c
+++ b/src/core/windows/SDL_windows.c
@@ -331,6 +331,78 @@ void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)
     winrect->bottom = sdlrect->y + sdlrect->h - 1;
 }
 
+/* SDL_Win32RunApp(), which does most of the SDL_main work for Win32 */
+#ifdef __WIN32__
+
+#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;
+}
+
+DECLSPEC int
+SDL_Win32RunApp(SDL_main_func mainFunction, void * xamlBackgroundPanel)
+{
+
+    /* Gets the arguments with GetCommandLine, converts them to argc and argv
+       and calls SDL_main */
+
+    LPWSTR *argvw;
+    char **argv;
+    int i, argc, result;
+
+    argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
+    if (argvw == NULL) {
+        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.
+     */
+
+    /* 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) {
+        DWORD len;
+        char *arg = WIN_StringToUTF8W(argvw[i]);
+        if (arg == NULL) {
+            return OutOfMemory();
+        }
+        len = (DWORD)SDL_strlen(arg);
+        argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len + 1);
+        if (!argv[i]) {
+            return OutOfMemory();
+        }
+        SDL_memcpy(argv[i], arg, len);
+        SDL_free(arg);
+    }
+    argv[i] = NULL;
+    LocalFree(argvw);
+
+    SDL_SetMainReady();
+
+    /* Run the application main() code */
+    result = mainFunction(argc, argv);
+
+    /* Free argv, to avoid memory leak */
+    for (i = 0; i < argc; ++i) {
+        HeapFree(GetProcessHeap(), 0, argv[i]);
+    }
+    HeapFree(GetProcessHeap(), 0, argv);
+
+    return result;
+}
+
+#endif /* __WIN32__ */
+
 #endif /* defined(__WIN32__) || defined(__WINRT__) || defined(__GDK__) */
 
 /*
diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c
index 62625049c2a7..d1f627b59787 100644
--- a/src/dynapi/SDL_dynapi.c
+++ b/src/dynapi/SDL_dynapi.c
@@ -31,6 +31,9 @@
 #endif
 
 #include <SDL3/SDL.h>
+#define _SDL_MAIN_NOIMPL /* don't drag in header-only implementation of SDL_main */
+#include <SDL3/SDL_main.h>
+
 
 /* These headers have system specific definitions, so aren't included above */
 #include <SDL3/SDL_syswm.h>
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index a3ebfd3df140..785c1515be59 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -861,6 +861,7 @@ SDL3_0.0.0 {
     SDL_DelayNS;
     SDL_GetEventState;
     SDL_GetRenderDriver;
+    SDL_Win32RunApp;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 230abdb6878b..44575201c149 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -885,3 +885,4 @@
 #define SDL_DelayNS SDL_DelayNS_REAL
 #define SDL_GetEventState SDL_GetEventState_REAL
 #define SDL_GetRenderDriver SDL_GetRenderDriver_REAL
+#define SDL_Win32RunApp SDL_Win32RunApp_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 7915e6c9e024..dd7707b50bd5 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -961,3 +961,6 @@ SDL_DYNAPI_PROC(Uint64,SDL_GetTicksNS,(void),(),return)
 SDL_DYNAPI_PROC(void,SDL_DelayNS,(Uint64 a),(a),)
 SDL_DYNAPI_PROC(Uint8,SDL_GetEventState,(Uint32 a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetRenderDriver,(int a),(a),return)
+#if defined(__WIN32__)
+SDL_DYNAPI_PROC(int,SDL_Win32RunApp,(SDL_main_func a, void *b),(a,b),return)
+#endif
diff --git a/src/main/windows/SDL_windows_main.c b/src/main/windows/SDL_windows_main.c
index 5f65d5b0378d..36bad6ea43c7 100644
--- a/src/main/windows/SDL_windows_main.c
+++ b/src/main/windows/SDL_windows_main.c
@@ -1,110 +1,8 @@
 /*
     SDL_windows_main.c, placed in the public domain by Sam Lantinga  4/13/98
 
-    The WinMain function -- calls your program's main() function
+    Nothing to do here, the code moved into SDL_main_impl.h and SDL_windows.c (SDL_Win32RunApp())
+    TODO: remove this file
 */
-#include <SDL3/SDL.h>
-
-#ifdef __WIN32__
-
-/* Include this so we define UNICODE properly */
-#include "../../core/windows/SDL_windows.h"
-#include <shellapi.h> /* CommandLineToArgvW() */
-
-#ifdef main
-#undef main
-#endif /* main */
-
-/* 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;
-}
-
-#if defined(_MSC_VER)
-/* The VC++ compiler needs main/wmain defined */
-#define console_ansi_main main
-#if UNICODE
-#define console_wmain wmain
-#endif
-#endif
-
-/* Gets the arguments with GetCommandLine, converts them to argc and argv
-   and calls SDL_main */
-static int main_getcmdline(void)
-{
-    LPWSTR *argvw;
-    char **argv;
-    int i, argc, result;
-
-    argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
-    if (argvw == NULL) {
-        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.
-     */
-
-    /* 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) {
-        DWORD len;
-        char *arg = WIN_StringToUTF8W(argvw[i]);
-        if (arg == NULL) {
-            return OutOfMemory();
-        }
-        len = (DWORD)SDL_strlen(arg);
-        argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size_t)len + 1);
-        if (!argv[i]) {
-            return OutOfMemory();
-        }
-        SDL_memcpy(argv[i], arg, len);
-        SDL_free(arg);
-    }
-    argv[i] = NULL;
-    LocalFree(argvw);
-
-    SDL_SetMainReady();
-
-    /* Run the application main() code */
-    result = SDL_main(argc, argv);
-
-    /* Free argv, to avoid memory leak */
-    for (i = 0; i < argc; ++i) {
-        HeapFree(GetProcessHeap(), 0, argv[i]);
-    }
-    HeapFree(GetProcessHeap(), 0, argv);
-
-    return result;
-}
-
-/* This is where execution begins [console apps, ansi] */
-int console_ansi_main(int argc, char *argv[])
-{
-    return main_getcmdline();
-}
-
-#if UNICODE
-/* This is where execution begins [console apps, unicode] */
-int console_wmain(int argc, wchar_t *wargv[], wchar_t *wenvp)
-{
-    return main_getcmdline();
-}
-#endif
-
-/* This is where execution begins [windowed apps] */
-int WINAPI
-WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) /* NOLINT(readability-inconsistent-declaration-parameter-name) */
-{
-    return main_getcmdline();
-}
-
-#endif /* __WIN32__ */
 
 /* vi: set ts=4 sw=4 expandtab: */