WINMAIN and main() in C++ (Extended)
Asked Answered
C

8

85

Right, I have looked at this post: Difference between WinMain,main and DllMain in C++

I now know that WINMAIN is used for window applications and main() for consoles. But reading the post doesn't really tell me why exactly what is the difference.

I mean what's the point of having separating different mains functions to start of a program? Is it due to performance issues? Or what is it?

Crossquestion answered 14/12, 2012 at 1:58 Comment(1)
Depends what you want passed into your application. int main() is always allowed as an entry point according to the standard, though, along with int main(int, char**).Tallowy
G
227

About the functions.

The C and C++ standards require any program (for a “hosted” C or C++ implementation) to have a function called main, which serves as the program's startup function. The main function is called after zero-initialization of non-local static variables, and possibly but not necessarily (!, C++11 §3.6.2/4) this call happens after dynamic initialization of such variables. It can have one of the following signatures:

int main()
int main( int argc, char* argv[] )

plus possible implementation-defined signatures (C++11 §3.6.1/2) except that the result type must be int.

As the only such function in C++ main has a default result value, namely 0. If main returns then after the ordinary function return exit is called with the main result value as argument. The standard defines three values that guaranteed can be used: 0 (indicates success), EXIT_SUCCESS (also indicates success, and is typically defined as 0), and EXIT_FAILURE (indicates failure), where the two named constants are defined by the <stdlib.h> header which also declares the exit function.

The main arguments are intended to represent the command line arguments for the command used to start the process. argc (argument count) is the number of items in the argv (argument values) array. In addition to those items argv[argc] is guaranteed to be 0. If argc > 0 – which is not guaranteed! – then argv[0] is guaranteed to either be a pointer to an empty string, or a pointer to the “name used to invoke the program”. This name may include a path, and it may be the name of the executable.

Using the main arguments to obtain the command line arguments works fine in *nix, because C and C++ originated with *nix. However, the de facto Windows standard for the encoding of the main arguments is Windows ANSI, which does not support general Windows filenames (such as, for a Norwegian Windows installation, filenames with Greek or Cyrillic characters). Therefore Microsoft chose to extend the C and C++ languages with a Windows-specific startup function called wmain, which has wide character based arguments encoded as UTF-16, which can represent any filename.

The wmain function can have one of these signatures, corresponding to the standard signatures for main:

int wmain()
int wmain( int argc, wchar_t* argv[] )

plus a few more that are not especially useful.

I.e., wmain is a direct wide character based replacement for main.

The WinMain char based function was introduced with Windows, in the early 1980's:

int CALLBACK WinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    LPSTR       lpCmdLine,
    int         nCmdShow
    );

where CALLBACK, HINSTANCE and LPSTR are defined by the <windows.h> header (LPSTR is just char*).

Arguments:

  • the hInstance argument value is the base address of the memory image of the executable, it's primarily used to load resources from the executable, and it can alternatively be obtained from the GetModuleHandle API function,

  • the hPrevInstance argument is always 0,

  • the lpCmdLine argument can alternatively be obtained from the GetCommandLine API function, plus a bit of weird logic to skip the program name part of the command line, and

  • the nCmdShow argument value can alternatively be obtained from the GetStartupInfo API function, but with modern Windows the first creation of a top level window does that automatically so it's not of any practical use.

Thus, the WinMain function has the same drawbacks as standard main, plus some (in particular the verbosity and being non-standard), and no advantages of its own, so it's really inexplicable except possibly as a vendor lock-in thing. However, with the Microsoft tool chain it makes the linker default to the GUI subsystem, which some see as an advantage. But with e.g. the GNU toolchain it does not have such an effect so this effect cannot be relied on.

The wWinMain wchar_t based function is a wide character variant of WinMain, in the same way as wmain is a wide character variant of standard main:

int WINAPI wWinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    PWSTR       lpCmdLine,
    int         nCmdShow
    );

where WINAPI is the same as CALLBACK, and PWSTR is simply wchar_t*.

There is no good reason to use any of the non-standard functions except the least known and least supported of them, namely wmain, and then just for convenience: that this avoids using the GetCommandLine and CommandLineToArgvW API functions to pick up UTF-16 encoded arguments.

To avoid the Microsoft linker acting up (the GNU toolchain's linker doesn't), just set the LINK environment variable to /entry:mainCRTStartup, or specify that option directly. This is the Microsoft runtime library entry point function that, after some initialization, calls the standard main function. The other startup functions have corresponding entry point functions named in the same systematic way.


Examples of using the standard main function.

Common source code:

    foo.cpp

#undef UNICODE
#define UNICODE
#include <windows.h>

int main()
{
    MessageBox( 0, L"Press OK", L"Hi", MB_SETFOREGROUND );
}

In the examples below (first with the GNU toolchain and then with the Microsoft toolchain) this program is first built as a console subsystem program, and then as a GUI subsystem program. A console subsystem program, or in short just a console program, is one that requires a console window. This is the default subsystem for all Windows linkers I've used (admittedly not a great many), possibly for all Windows linkers period.

For a console program Windows creates a console window automatically if needed. Any Windows process, regardless of subsystem, can have an associated console window, and at most one. Also, the Windows command interpreter waits for a console program program to finish, so that the program's text presentation has finished.

Conversely, a GUI subsystem program is one that doesn't require a console window. The command interpreter does not wait for a GUI subsystem program, except in batch files. One way to avoid the completion wait, for both kinds of program, is to use the start command. One way to present console window text from a GUI subsystem program is to redirect its standard output stream. Another way is to explicitly create a console window from the program's code.

The program's subsystem is encoded in the executable's header. It's not shown by Windows Explorer (except that in Windows 9x one could “quick view” an executable, which presented just about the same information as Microsoft's dumpbin tool now does). There is no corresponding C++ concept.

main with the GNU toolchain.

[D:\dev\test]
> g++ foo.cpp

[D:\dev\test]
> objdump -x a.exe | find /i "subsys"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000003        (Windows CUI)
[544](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[612](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000003 __subsystem__
[636](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> g++ foo.cpp -mwindows

[D:\dev\test]
> objdump -x a.exe | find /i "subsys"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000002        (Windows GUI)
[544](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[612](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000002 __subsystem__
[636](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> _

main with Microsoft's toolchain:

[D:\dev\test]
> set LINK=/entry:mainCRTStartup

[D:\dev\test]
> cl foo.cpp user32.lib
foo.cpp

[D:\dev\test]
> dumpbin /headers foo.exe | find /i "subsys"
            6.00 subsystem version
               3 subsystem (Windows CUI)

[D:\dev\test]
> cl foo.cpp /link user32.lib /subsystem:windows
foo.cpp

[D:\dev\test]
> dumpbin /headers foo.exe | find /i "subsys"
            6.00 subsystem version
               2 subsystem (Windows GUI)

[D:\dev\test]
> _

Examples of using Microsoft’s wmain function.

The following main code is common to both the GNU toolchain and Microsoft toolchain demonstrations:

    bar.cpp

#undef UNICODE
#define UNICODE
#include <windows.h>

#include <string>       // std::wstring
#include <sstream>      // std::wostringstream
using namespace std;

int wmain( int argc, wchar_t* argv[] )
{
    wostringstream  text;

    text << argc - 1 << L" command line arguments:\n";
    for( int i = 1;  i < argc;  ++i )
    {
        text << "\n[" << argv[i] << "]";
    }

    MessageBox( 0, text.str().c_str(), argv[0], MB_SETFOREGROUND );
}

wmain with the GNU toolchain.

The GNU toolchain doesn't support Microsoft's wmain function:

[D:\dev\test]
> g++ bar.cpp
d:/bin/mingw/bin/../lib/gcc/i686-pc-mingw32/4.7.1/../../../libmingw32.a(main.o):main.c:(.text.startup+0xa3): undefined reference to `WinMain
@16'
collect2.exe: error: ld returned 1 exit status

[D:\dev\test]
> _

The link error message here, about WinMain, is because the GNU toolchain does support that function (presumably because so much ancient code uses it), and searches for it as a last resort after failing to find a standard main.

However, it's trivial to add a module with a standard main that calls the wmain:

    wmain_support.cpp

extern int wmain( int, wchar_t** );

#undef UNICODE
#define UNICODE
#include <windows.h>    // GetCommandLine, CommandLineToArgvW, LocalFree

#include <stdlib.h>     // EXIT_FAILURE

int main()
{
    struct Args
    {
        int n;
        wchar_t** p;

        ~Args() {  if( p != 0 ) { ::LocalFree( p ); } }
        Args(): p(  ::CommandLineToArgvW( ::GetCommandLine(), &n ) ) {}
    };

    Args    args;

    if( args.p == 0 )
    {
        return EXIT_FAILURE;
    }
    return wmain( args.n, args.p );
}

Now,

[D:\dev\test]
> g++ bar.cpp wmain_support.cpp

[D:\dev\test]
> objdump -x a.exe | find /i "subsystem"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000003        (Windows CUI)
[13134](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[13576](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000003 __subsystem__
[13689](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> g++ bar.cpp wmain_support.cpp -mwindows

[D:\dev\test]
> objdump -x a.exe | find /i "subsystem"
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Subsystem               00000002        (Windows GUI)
[13134](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000004 __major_subsystem_version__
[13576](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000002 __subsystem__
[13689](sec -1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x00000000 __minor_subsystem_version__

[D:\dev\test]
> _

wmain with Microsoft’s toolchain.

With Microsoft's toolchain the linker automatically infers the wmainCRTStartup entry point if no entry point is specified and a wmain function is present (it's unclear what happens if a standard main is also present, I haven't checked that in recent years):

[D:\dev\test]
> set link=/entry:mainCRTStartup

[D:\dev\test]
> cl bar.cpp user32.lib
bar.cpp
LIBCMT.lib(crt0.obj) : error LNK2019: unresolved external symbol _main referenced in function ___tmainCRTStartup
bar.exe : fatal error LNK1120: 1 unresolved externals

[D:\dev\test]
> set link=

[D:\dev\test]
> cl bar.cpp user32.lib
bar.cpp

[D:\dev\test]
> _

With a non-standard startup function such as wmain it is, however, probably best to specify the entry point explicitly, so as to be very clear about the intention:

[D:\dev\test]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup
bar.cpp

[D:\dev\test]
> dumpbin /headers bar.exe | find /i "subsystem"
            6.00 subsystem version
               3 subsystem (Windows CUI)

[D:\dev\test]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup /subsystem:windows
bar.cpp

[D:\dev\test]
> dumpbin /headers bar.exe | find /i "subsystem"
            6.00 subsystem version
               2 subsystem (Windows GUI)

[D:\dev\test]
> _
Garish answered 14/12, 2012 at 1:59 Comment(2)
Can anyone update the link of valid winmain() signatures?Fagoting
@jvriesem: WinMain has only one valid signature and so also wWinMain. However, wmain is like standard main, only with wide string arguments. That means it supports int wmain(), int wmain( int, wchar_t** ) like standard main, and int wmain( int, wchar_t**, wchar** ) as an extension. The 3ʳᵈ argument, typically called envp, is a pointer to environment variable strings where “This array is terminated by a NULL entry” according to current Microsoft docs. Now silly SO refuses more text, so.Garish
V
15

According to @RaymondChen

The name WinMain is just a convention

Although the function WinMain is documented in the Platform SDK, it's not really part of the platform. Rather, WinMain is the conventional name for the user-provided entry point to a Windows program.

The real entry point is in the C runtime library, which initializes the runtime, runs global constructors, and then calls your WinMain function (or wWinMain if you prefer a Unicode entry point).

DllMain and WinMain is different in their prototypes itself. WinMain accepts commandline argument while the other one talks about how it's attached to the process.

As per MSDN documentation

By default, the starting address is a function name from the C run-time library. The linker selects it according to the attributes of the program, as shown in the following table.

  • mainCRTStartup (or wmainCRTStartup) An application using /SUBSYSTEM:CONSOLE; calls main (or wmain)

  • WinMainCRTStartup (or wWinMainCRTStartup) An application using /SUBSYSTEM:WINDOWS; calls WinMain (or wWinMain), which must be defined with __stdcall

  • _DllMainCRTStartup A DLL; calls DllMain, which must be defined with __stdcall, if it exists

Voltaic answered 14/12, 2012 at 3:37 Comment(0)
B
4

The Windows CRT exposes 5 symbols mainCRTStartup, wmainCRTStartup, wWinMainCRTStartup, _DllMainCRTStartup and WinMainCRTStartup.

enter image description here

The w before means the unicode version, i.e. the command line (command and argument string) passed to the process (and stored in the PPB PEB->ProcessParameters->CommandLine) is UTF-16 (i.e. WCHAR (Wide char)) rather than ASCII (UTF-8 or ANSI e.g. Windows-1252, Windows-850 etc.)

If the /DLL or /SUBSYSTEM option is not specified, the MSVC linker selects a subsystem and entry point i.e. makes the entry address one of those 5 functions based on whether main, WinMain or DllMain is present in the symbol table of one of the object files. The entry point is statically linked by the MSVC linker via libcmt.lib, which will further only include symbols that are actually used by that entry function.

mainCRTStartup will call GetStartupInfo() / access the PPB to get stdin/out handles and command line arguments. _init term() is also called, as well as _init_atexit(). It calls main The return value of main is passed to ExitProcess() after the set atexit routine is called.

Debug symbols in a .pdb created by using the /Zi compiler option are needed to be loaded in IDA Pro in order to reveal symbols such as mainCRTstartup and main, which will without debugging symbols instead be represented as start and sub_?????? respectively.

Reference:

Bannasch answered 7/4, 2020 at 0:50 Comment(0)
D
3

A standard C program is passed 2 parameters by the command line at start up:

int main( int argc, char** argv ) ;
  • char** argv is an array of strings (char*)
  • int argc is the number of char* in argv

The booting function WinMain that programmers have to write for a windows program is slightly different. WinMain takes 4 parameters that are passed to the program by Win O/S at start up:

int WINAPI WinMain( HINSTANCE hInstance,    // HANDLE TO AN INSTANCE.  This is the "handle" to YOUR PROGRAM ITSELF.
                    HINSTANCE hPrevInstance,// USELESS on modern windows (totally ignore hPrevInstance)
                    LPSTR szCmdLine,        // Command line arguments.  similar to argv in standard C programs
                    int iCmdShow )          // Start window maximized, minimized, etc.

See my article How to create a basic window in C for more

Dvinsk answered 14/12, 2012 at 2:3 Comment(11)
-1 several wrong claims: "have to write", nope. "passed ... by Win O/S", nope. in addition a bit of terminology: "booting function", nope.Garish
Well, "have to write" is wrong because you can always use a standard main for an application, regardless of Windows subsystem. "passed ... by Win O/S" is wrong because those parameters are not passed by the OS. they're passed by the entry point function, which fetches the information from the OS e.g. by calling GetCommandLine. "booting function" is misleading because there's no booting involved.Garish
It seems to me that the answer is useless. QA asked what's the difference and you say about the differences in function signature. Isn't it obvious?Pneumonia
@Cheersandhth.-Alf, is your comment in regards to the programming language of the answer (C) or the programming language of the question (C++)?Threap
@JustBeingHelpful: the programming language doesn't matter except that implementations of other languages may not support any notion of WinMain. It's a difficult topic to discuss because Microsoft's documentation is pure fantasy, and e.g. currently alleges that WinMain is called from a generated main (which is nonsense, you can use the debugger to check the implementation). The documentation has varied with various mutually incompatible fantasy descriptions since the 1990's, but it is at least consistently wrong; that one can rely on, so far.Garish
Some more info on Windows app start/entry point hereDvinsk
Though the official MS docs say WinMain is the app entry pointDvinsk
@Cheersandhth.-Alf, I'm sure with you having so much experience being a programmer, you can probably fix any program under the sun, but when source code is not available, like Microsoft's, you run into quite a few problems nobody can fix except Microsoft developers who have the knowledge and authority to fix them. So I feel your pain.Threap
This was the issue I ran into for anyone interested. I hadn't compiled C program in 22 years. https://mcmap.net/q/239139/-winmain-and-main-in-c-extendedThreap
@JustBeingHelpful: re "but when source code is not available, like Microsoft's", I'll take your word for it that it's no longer available. It used to be. I learned how these things works (in particular the undocumented machine code entry point) by inspecting the source code, via the Visual Studio debugger.Garish
@Cheersandhth.-Alf, I didn't expect their source code to be available for these tools, so I should not have said it's not available. I will try and find the source code.Threap
B
2

I vaguely recall reading somewhere that Windows programs have a main() function. It is just hidden in a header or library somewhere. I believe this main() function initializes all of the variables needed by WinMain() and then calls it.

Of course, I'm a WinAPI noob, so I hope others who are more knowledgeable will correct me if I am wrong.

Brief answered 14/12, 2012 at 2:9 Comment(1)
If this is true and my thinking is correct, this means we can hide console window without -mwindows, by simulating what the "hidden main()" does.Pampa
G
0

I had an exe using _tWinMain and Configuration Properties.Linker.System.Subsystem: Windows (/SUBSYSTEM:WINDOWS). Later I wanted it to support cmdline args and print to the console so I added:

// We need to printf to stdout and we don't have one, so get one
AllocConsole();
// Redirect pre-opened STDOUT to the console
freopen_s((FILE **)stdout, "CONOUT$", "w", stdout);

but that only worked by printing in another console window that went away so was not that useful. Below is the way I changed it to work with Console (/SUBSYSTEM:CONSOLE) in such a way as I could go back and forth, if I needed to.

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  UNREFERENCED_PARAMETER(envp);
  return (_tWinMain(NULL, NULL, ::GetCommandLineW(), 0));
}
Giotto answered 11/12, 2018 at 20:40 Comment(0)
F
0

Main vs WinMain

As I have read from many links:

WinMain() is the C entry point function of any windows application. Like normal DOS/console based application which has main() function as C entry point, in windows we have WinMain() instead. WinMain() is a function which is called by system during creation of a process.

First argument is the instance handle of the current process.

Next is the previous instance.

Command line arguments comes as next argument.

Finally shell passes the show/display attribute of main window.

Note : WinMain returns success as zero and error as non zero.

Freund answered 8/1, 2020 at 10:10 Comment(0)
T
0

For any Microsoft developers who have access to and understand the source code for the tools (including Developer Command Prompt for VS 2022) installed in this link to setup C/C++ in Visual Studio Code, which is what most modern developers use, notice how this error message only happens once when compiling (Step 2). Then the error message never appears again. So the Developer Command Prompt for VS 2022 source code must have two different sets of logic. One set of logic before this error appears and one set of logic after. Strange.

This question led me to the question we're in here.

LINK : fatal error LNK1561: entry point must be defined ERROR IN VC++

Step 1.) Install these tools:

https://visualstudio.microsoft.com/visual-cpp-build-tools/

Step 2.) Compile the hello.c program below and get error message

LINK : fatal error LNK1561: entry point must be defined

Step 3.) Add parameters to main() to look like this again (reverted) and compile again with cl hello.c, and no error results.

int main(int argc, char** argv)

Step 4.) Remove parameters from main() to look like this again and compile again with cl hello.c, and no error results.

int main()

Source code:

#include <stdio.h>


int main()
{
    printf("Hello, World! This is a native C program compiled on the command line.\n");
    return 0;
}
Threap answered 27/4 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.