Win32 app without WinMain
2016-01-15
WinMain vs main
Win32 apps use WinMain
as an entry point. Most other platforms use main
. For a long time I’ve used one of two patterns for the entry point in cross platform projects.
- A separate entry point cpp file per platform with the win32 version containing
WinMain
and the others containingmain
. - Select entry point via preprocessor.
#ifdef _WIN32
int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, int )
#else
int main()
#endif
{
// do work
return 0;
}
These solutions are a ugly but acceptable until you start needing to parse command line arguments as well. WinMain provides lpCmdLine as one unsplit string. Handling command line arguments requires additional logic for handling unsplit args or splitting the string into argc, argv form.
This is starting to get complex. If I’m making many executables sharing the same code base, handling these variations repeatedly is tedious and error prone. If the programs are supposed to be easy to follow simple examples suddenly there is all this noise in the source code.
I’ve seen many libraries eliminate this duplication and complexity by bringing the entry point into the library and exposing a consistent library specific entry point the user is expected implement instead.
For example a cinder hello world looks like:
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
using namespace ci;
using namespace ci::app;
class BasicApp : public App {
public:
void draw() override;
};
void BasicApp::draw()
{
gl::clear();
}
CINDER_APP( BasicApp, RendererGl )
The problem with this approach is that now your library is a framework and frameworks don’t compose. Try to use use cinder with wxwidgets and you’ll immediately run into problems.
class MyApp: public wxApp
{
public:
virtual bool OnInit();
};
Setting the Subsystem
If we change the Win32 app to a Console app (/SUBSYSTEM:CONSOLE
) the application will use the main
entrypoint instead of the WinMain
.
Unfortunately this gives a dos console window. You can force this console window to close in code but it still flashes on startup which looks unpolished.
Close but no cigar.
The Trick
If we keep it a Win32 app (/SUBSYSTEM:WINDOWS
) but make it use the main
entry point by also passing /ENTRY:”mainCRTStartup”
to the linker we’ll get everything we want.
The application entry point functions main
and WinMain
have different signatures but the actual entry point functions for the executable don’t.
extern "C" int mainCRTStartup()
{
return __scrt_common_main();
}
extern "C" int WinMainCRTStartup()
{
return __scrt_common_main();
}
In fact they both call into the same function! This is because the CRT needs to be initialized before your application entry point, otherwise BAD THINGS™.
The magic happens in \Microsoft Visual Studio 14.0\VC\crt\src\vcruntime\exe_common.inl
.
#if defined _SCRT_STARTUP_MAIN
using main_policy = __scrt_main_policy;
using argv_policy = __scrt_narrow_argv_policy;
static void __cdecl initialize_environment() throw()
{
_initialize_narrow_environment();
}
static int __cdecl invoke_main() throw()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
#elif defined _SCRT_STARTUP_WMAIN
using main_policy = __scrt_main_policy;
using argv_policy = __scrt_wide_argv_policy;
static void __cdecl initialize_environment() throw()
{
_initialize_wide_environment();
}
static int __cdecl invoke_main() throw()
{
return wmain(__argc, __wargv, _get_initial_wide_environment());
}
#elif defined _SCRT_STARTUP_WINMAIN
using main_policy = __scrt_winmain_policy;
using argv_policy = __scrt_narrow_argv_policy;
static void __cdecl initialize_environment() throw()
{
_initialize_narrow_environment();
}
static int __cdecl invoke_main() throw()
{
return WinMain(
reinterpret_cast<HINSTANCE>(&__ImageBase),
nullptr,
_get_narrow_winmain_command_line(),
__scrt_get_show_window_mode());
}
#elif defined _SCRT_STARTUP_WWINMAIN
using main_policy = __scrt_winmain_policy;
using argv_policy = __scrt_wide_argv_policy;
static void __cdecl initialize_environment() throw()
{
_initialize_wide_environment();
}
static int __cdecl invoke_main() throw()
{
return wWinMain(
reinterpret_cast<HINSTANCE>(&__ImageBase),
nullptr,
_get_wide_winmain_command_line(),
__scrt_get_show_window_mode());
}
#endif
And thus we can make it call into main
as a Win32 app.
Missing Paramaters
You may notice that the WinMain
function gets passed some extra information that you don’t get when using main
.
You probably don’t need this information but if you really want it you can get it.
HINSTANCE hInstance
can be retrieved through GetModuleHandle(NULL)
.
int nCmdShow
can be retrieved by inspecting the wShowWindow
member of the STARTUPINFO
structure filled in by a call to GetStartupInfo
.
CMake Settings
In order to apply this trick to your CMake project configuration you can use something like:
add_executable( ${PROJECT_NAME} WIN32 ${SOURCES} )
set_target_properties( ${PROJECT_NAME} PROPERTIES LINK_FLAGS "/ENTRY:\"mainCRTStartup\"" )