Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Set C++ Application Entry Point to main() on Windows using CMake? [duplicate]

I have recently started using CMake, and was trying to build a GUI application, that doesn't have the console window on Windows. So in my CMakeLists.txt file, I did this:

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
    add_executable(${EXECUTABLE_NAME} main.cpp)
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
    add_executable(${EXECUTABLE_NAME} WIN32 main.cpp) #WIN32 So the console window does not open on Windows
endif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")

With this, the solution worked, and the console window does not open on Windows. However, this comes at a cost. When I try to build the solution, I realize that I have to change the signature of the function to WinMain, so I changed my main code to the following:

#ifdef _WIN32
#include <Windows.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, int) //Windows signature since creating WIN32 application without console
#else
int main()
#endif
{
    // ... GUI code
}

Unfortunately, I absolutely detest this, since it ruins the whole point of using CMake. I don't want to have to change anything in my code that is based on different platforms. This leads me to my question. How do I set the C++ application entry point to main() on Windows when making a GUI application without having to set it manually in Visual Studio? Can I do this directly in CMake using a cross-platform method? Or will I have to use the #if/#else/#endif solution? The only improvement to the solution above is using a macro MAIN_FUNCTION that does the preprocessor conditional. I want to avoid this as well.

On the other hand, is there another way to get rid of the console window in a GUI application on Windows that I didn't know of using CMake without using the WIN32 option?

like image 665
Arnav Borborah Avatar asked Aug 17 '17 00:08

Arnav Borborah


2 Answers

The solution is to add set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") before add_executable

It hides the console while still allowing you to have the usual int main() as the entry point.

like image 71
Gabriel Avatar answered Sep 28 '22 12:09

Gabriel


You're confusing two things here, but they are closely related.

The console that appears is a result of an application which has a Win32 header IMAGE_OPTIONAL_HEADER::Subsystem value of WINDOWS_CUI instead of WINDOWS_GUI. This is a Win32 thing, and it applies to all executables regardless of the language they're written in.

The entry point signature is a compiler-specific choice. It's the entry function called by the language runtime, not the OS. The OS calls the entry function of the language runtime, which first initialized that runtime and then hands off control to your entry point.

Now the VC++ compiler uses the CRT as the runtime. And that CRT runtime indeed uses two different signatures for your entry point. Obviously the implementation of std::cin has to work with WINDOWS_CUI, that's sort of the point of a Command-Line User Interface. But the same CRT also works with WINDOWS_GUI.

Here's where things get complex. You can actually change the Subsystem of a compiled application from CUI to GUI. The CRT won't mind, it's compatible with both subsystems. But since it's done to a compiled application, the call from one part of the application (CRT startup) to another (your entry point) isn't affected. This is a Win32 change, not a C++ change.

To get back to CMake: this subsystem change can be done either after CMake, or as a custom post-build step.

like image 21
MSalters Avatar answered Sep 28 '22 12:09

MSalters